gdharness 1.1.12 → 1.1.14
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 +724 -360
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +77 -134
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +81 -0
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +24 -57
- 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_screen.gd +50 -0
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +82 -16
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +4 -4
- package/build/index.js +605 -244
- 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;
|
|
@@ -7520,16 +7677,16 @@ var require_public_api = __commonJS(function(exports) {
|
|
|
7520
7677
|
// src/runner.ts
|
|
7521
7678
|
import { existsSync as existsSync3 } from "node:fs";
|
|
7522
7679
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7523
|
-
import
|
|
7680
|
+
import process3 from "node:process";
|
|
7524
7681
|
function currentRunner() {
|
|
7525
|
-
const versions =
|
|
7682
|
+
const versions = process3.versions;
|
|
7526
7683
|
return versions["bun"] === undefined ? "npx" : "bunx";
|
|
7527
7684
|
}
|
|
7528
7685
|
function runLine(runner, version, rest = "") {
|
|
7529
7686
|
const flag = runner === "npx" ? "-y " : "";
|
|
7530
7687
|
return `${runner} ${flag}gdharness@${version}${rest === "" ? "" : ` ${rest}`}`;
|
|
7531
7688
|
}
|
|
7532
|
-
function spawnFor(runner, version, execPath =
|
|
7689
|
+
function spawnFor(runner, version, execPath = process3.execPath) {
|
|
7533
7690
|
const spec = `gdharness@${version}`;
|
|
7534
7691
|
if (runner !== currentRunner()) {
|
|
7535
7692
|
return { command: runner, args: runner === "npx" ? ["-y", spec] : ["--bun", spec] };
|
|
@@ -7540,7 +7697,7 @@ function spawnFor(runner, version, execPath = process2.execPath) {
|
|
|
7540
7697
|
return { command: alongside(execPath, "npx") ?? "npx", args: ["-y", spec] };
|
|
7541
7698
|
}
|
|
7542
7699
|
function alongside(execPath, name) {
|
|
7543
|
-
const runner = join3(dirname2(execPath),
|
|
7700
|
+
const runner = join3(dirname2(execPath), process3.platform === "win32" ? `${name}.cmd` : name);
|
|
7544
7701
|
return existsSync3(runner) ? runner : undefined;
|
|
7545
7702
|
}
|
|
7546
7703
|
var init_runner = () => {};
|
|
@@ -7683,8 +7840,47 @@ var init_game_log = __esm(() => {
|
|
|
7683
7840
|
};
|
|
7684
7841
|
});
|
|
7685
7842
|
|
|
7843
|
+
// src/alive.ts
|
|
7844
|
+
import process5 from "node:process";
|
|
7845
|
+
function alive(pid) {
|
|
7846
|
+
if (pid === undefined || pid === null) {
|
|
7847
|
+
return false;
|
|
7848
|
+
}
|
|
7849
|
+
try {
|
|
7850
|
+
process5.kill(pid, 0);
|
|
7851
|
+
return true;
|
|
7852
|
+
} catch (error) {
|
|
7853
|
+
return error.code === "EPERM";
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
var init_alive = () => {};
|
|
7857
|
+
|
|
7686
7858
|
// src/scratch.ts
|
|
7687
|
-
import { rmSync as rmSync3 } from "node:fs";
|
|
7859
|
+
import { existsSync as existsSync5, mkdtempSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
7860
|
+
import { tmpdir } from "node:os";
|
|
7861
|
+
import { join as join5 } from "node:path";
|
|
7862
|
+
import process6 from "node:process";
|
|
7863
|
+
function scratchDirectory(kind) {
|
|
7864
|
+
return mkdtempSync(join5(tmpdir(), `gdharness-${kind}-${process6.pid}-`));
|
|
7865
|
+
}
|
|
7866
|
+
function sweepAbandonedScratch(directory = tmpdir()) {
|
|
7867
|
+
let entries;
|
|
7868
|
+
try {
|
|
7869
|
+
entries = readdirSync2(directory);
|
|
7870
|
+
} catch {
|
|
7871
|
+
return;
|
|
7872
|
+
}
|
|
7873
|
+
const named = new RegExp(`^gdharness-(${KINDS.join("|")})-(\\d+)-[A-Za-z0-9]{6}$`);
|
|
7874
|
+
for (const entry of entries) {
|
|
7875
|
+
const owner = named.exec(entry)?.[2];
|
|
7876
|
+
if (owner === undefined || alive(Number(owner))) {
|
|
7877
|
+
continue;
|
|
7878
|
+
}
|
|
7879
|
+
try {
|
|
7880
|
+
remove(join5(directory, entry));
|
|
7881
|
+
} catch {}
|
|
7882
|
+
}
|
|
7883
|
+
}
|
|
7688
7884
|
function discardWith(remove, paths) {
|
|
7689
7885
|
const left = [];
|
|
7690
7886
|
for (const path of paths) {
|
|
@@ -7700,20 +7896,75 @@ function discardWith(remove, paths) {
|
|
|
7700
7896
|
return left;
|
|
7701
7897
|
}
|
|
7702
7898
|
function discard(...paths) {
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7899
|
+
const left = discardWith(remove, paths);
|
|
7900
|
+
for (const { path } of left) {
|
|
7901
|
+
tryAgain(path, 0);
|
|
7902
|
+
}
|
|
7903
|
+
return left;
|
|
7904
|
+
}
|
|
7905
|
+
function remove(path) {
|
|
7906
|
+
rmSync3(path, SWEPT);
|
|
7907
|
+
}
|
|
7908
|
+
function tryAgain(path, attempt) {
|
|
7909
|
+
if (attempt === 0) {
|
|
7910
|
+
if (waiting.has(path)) {
|
|
7911
|
+
return;
|
|
7912
|
+
}
|
|
7913
|
+
waiting.add(path);
|
|
7914
|
+
atExit.delete(path);
|
|
7915
|
+
lastTryOnExit();
|
|
7916
|
+
}
|
|
7917
|
+
const after = AGAIN_AFTER_MS[attempt];
|
|
7918
|
+
if (after === undefined || !existsSync5(path)) {
|
|
7919
|
+
waiting.delete(path);
|
|
7920
|
+
if (after === undefined) {
|
|
7921
|
+
atExit.add(path);
|
|
7922
|
+
}
|
|
7923
|
+
return;
|
|
7924
|
+
}
|
|
7925
|
+
setTimeout(() => {
|
|
7926
|
+
try {
|
|
7927
|
+
remove(path);
|
|
7928
|
+
waiting.delete(path);
|
|
7929
|
+
} catch {
|
|
7930
|
+
tryAgain(path, attempt + 1);
|
|
7931
|
+
}
|
|
7932
|
+
}, after).unref();
|
|
7706
7933
|
}
|
|
7707
|
-
|
|
7934
|
+
function lastTryOnExit() {
|
|
7935
|
+
if (lastTryWatched) {
|
|
7936
|
+
return;
|
|
7937
|
+
}
|
|
7938
|
+
lastTryWatched = true;
|
|
7939
|
+
process6.once("exit", () => {
|
|
7940
|
+
for (const path of [...waiting, ...atExit]) {
|
|
7941
|
+
try {
|
|
7942
|
+
remove(path);
|
|
7943
|
+
} catch {}
|
|
7944
|
+
}
|
|
7945
|
+
});
|
|
7946
|
+
}
|
|
7947
|
+
var SWEPT, AGAIN_AFTER_MS, waiting, atExit, KINDS, lastTryWatched = false;
|
|
7708
7948
|
var init_scratch = __esm(() => {
|
|
7709
|
-
|
|
7949
|
+
init_alive();
|
|
7950
|
+
SWEPT = { recursive: true, force: true };
|
|
7951
|
+
AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
7952
|
+
waiting = new Set;
|
|
7953
|
+
atExit = new Set;
|
|
7954
|
+
KINDS = [
|
|
7955
|
+
"desktop",
|
|
7956
|
+
"export",
|
|
7957
|
+
"import",
|
|
7958
|
+
"params",
|
|
7959
|
+
"runtime-screenshot",
|
|
7960
|
+
"tests"
|
|
7961
|
+
];
|
|
7710
7962
|
});
|
|
7711
7963
|
|
|
7712
7964
|
// src/headless.ts
|
|
7713
7965
|
import { execFile as execFile3 } from "node:child_process";
|
|
7714
|
-
import {
|
|
7715
|
-
import {
|
|
7716
|
-
import { join as join5 } from "node:path";
|
|
7966
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
7967
|
+
import { join as join6 } from "node:path";
|
|
7717
7968
|
import { promisify as promisify3 } from "node:util";
|
|
7718
7969
|
function snakeCased(params) {
|
|
7719
7970
|
const result = emptyRecord();
|
|
@@ -7753,9 +8004,9 @@ function reason(stdout, stderr) {
|
|
|
7753
8004
|
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
7754
8005
|
}
|
|
7755
8006
|
async function runImport(godotPath, projectPath) {
|
|
7756
|
-
const logDir =
|
|
8007
|
+
const logDir = scratchDirectory("import");
|
|
7757
8008
|
try {
|
|
7758
|
-
const { stderr } = await run3(godotPath, ["--headless", "--log-file",
|
|
8009
|
+
const { stderr } = await run3(godotPath, ["--headless", "--log-file", join6(logDir, "engine.log"), "--path", projectPath, "--import"], { signal: callSignal() });
|
|
7759
8010
|
return { ok: true, messages: problems(stderr) };
|
|
7760
8011
|
} catch (error) {
|
|
7761
8012
|
if (error instanceof Error && "stdout" in error && "stderr" in error) {
|
|
@@ -7776,13 +8027,13 @@ async function runImport(godotPath, projectPath) {
|
|
|
7776
8027
|
}
|
|
7777
8028
|
}
|
|
7778
8029
|
async function runOperation(engine, operation, params, projectPath) {
|
|
7779
|
-
const paramsDir =
|
|
7780
|
-
const paramsFile =
|
|
8030
|
+
const paramsDir = scratchDirectory("params");
|
|
8031
|
+
const paramsFile = join6(paramsDir, `${operation}.json`);
|
|
7781
8032
|
writeFileSync3(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
7782
8033
|
const args = [
|
|
7783
8034
|
"--headless",
|
|
7784
8035
|
"--log-file",
|
|
7785
|
-
|
|
8036
|
+
join6(paramsDir, "engine.log"),
|
|
7786
8037
|
"--path",
|
|
7787
8038
|
projectPath,
|
|
7788
8039
|
"--script",
|
|
@@ -7831,9 +8082,9 @@ var init_headless = __esm(() => {
|
|
|
7831
8082
|
});
|
|
7832
8083
|
|
|
7833
8084
|
// src/update-check.ts
|
|
7834
|
-
import { existsSync as
|
|
8085
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7835
8086
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
7836
|
-
import { join as
|
|
8087
|
+
import { join as join7 } from "node:path";
|
|
7837
8088
|
function packageUrl(root) {
|
|
7838
8089
|
return `${root.replace(/\/+$/, "")}/gdharness/latest`;
|
|
7839
8090
|
}
|
|
@@ -7855,25 +8106,25 @@ function cacheDirectory(environment) {
|
|
|
7855
8106
|
};
|
|
7856
8107
|
const home = set("HOME") ?? homedir3();
|
|
7857
8108
|
if (process.platform === "win32") {
|
|
7858
|
-
return
|
|
8109
|
+
return join7(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
7859
8110
|
}
|
|
7860
8111
|
if (process.platform === "darwin") {
|
|
7861
|
-
return
|
|
8112
|
+
return join7(home, "Library", "Caches", "gdharness");
|
|
7862
8113
|
}
|
|
7863
|
-
return
|
|
8114
|
+
return join7(set("XDG_CACHE_HOME") ?? join7(home, ".cache"), "gdharness");
|
|
7864
8115
|
}
|
|
7865
8116
|
function cacheFile(environment = process.env) {
|
|
7866
8117
|
try {
|
|
7867
8118
|
const directory = cacheDirectory(environment);
|
|
7868
8119
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
7869
|
-
return
|
|
8120
|
+
return join7(directory, "update-check.json");
|
|
7870
8121
|
} catch {
|
|
7871
|
-
return
|
|
8122
|
+
return join7(tmpdir2(), "gdharness-update-check.json");
|
|
7872
8123
|
}
|
|
7873
8124
|
}
|
|
7874
8125
|
function readCache(path) {
|
|
7875
8126
|
try {
|
|
7876
|
-
if (!
|
|
8127
|
+
if (!existsSync6(path)) {
|
|
7877
8128
|
return null;
|
|
7878
8129
|
}
|
|
7879
8130
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
@@ -8076,19 +8327,20 @@ var init_server_version = __esm(() => {
|
|
|
8076
8327
|
|
|
8077
8328
|
// src/issues.ts
|
|
8078
8329
|
import { createHash } from "node:crypto";
|
|
8079
|
-
import
|
|
8080
|
-
function feedbackNotice() {
|
|
8330
|
+
import process7 from "node:process";
|
|
8331
|
+
function feedbackNotice(every) {
|
|
8081
8332
|
return JSON.stringify({
|
|
8082
8333
|
gdharness_feedback: {
|
|
8334
|
+
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
8335
|
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
8336
|
enhancement_url: ENHANCEMENT_URL
|
|
8085
8337
|
}
|
|
8086
8338
|
}, null, 2);
|
|
8087
8339
|
}
|
|
8088
8340
|
function runtime() {
|
|
8089
|
-
const versions =
|
|
8341
|
+
const versions = process7.versions;
|
|
8090
8342
|
const bun = versions["bun"];
|
|
8091
|
-
return bun === undefined ? `node ${
|
|
8343
|
+
return bun === undefined ? `node ${process7.versions.node}` : `bun ${bun}`;
|
|
8092
8344
|
}
|
|
8093
8345
|
function defectSignature(where, message) {
|
|
8094
8346
|
const normalised = `${where}
|
|
@@ -8100,7 +8352,7 @@ function facts(where, message, godotVersion) {
|
|
|
8100
8352
|
["Where", where],
|
|
8101
8353
|
["Error", message],
|
|
8102
8354
|
["Version", `gdharness ${SERVER_VERSION}`],
|
|
8103
|
-
["Runtime", `${runtime()}, ${
|
|
8355
|
+
["Runtime", `${runtime()}, ${process7.platform} ${process7.arch}`],
|
|
8104
8356
|
["Godot", godotVersion ?? "not known at the point this failed"],
|
|
8105
8357
|
["Signature", defectSignature(where, message)]
|
|
8106
8358
|
];
|
|
@@ -8110,7 +8362,7 @@ function filledTemplate(where, message, godotVersion) {
|
|
|
8110
8362
|
`**gdharness version**: ${SERVER_VERSION}`,
|
|
8111
8363
|
`**Godot version**: ${godotVersion ?? ""}`,
|
|
8112
8364
|
`**Bun version**: ${runtime()}`,
|
|
8113
|
-
`**OS**: ${
|
|
8365
|
+
`**OS**: ${process7.platform} ${process7.arch}`,
|
|
8114
8366
|
"**MCP client**:",
|
|
8115
8367
|
"",
|
|
8116
8368
|
"## What you did",
|
|
@@ -8199,35 +8451,82 @@ var init_issues = __esm(() => {
|
|
|
8199
8451
|
});
|
|
8200
8452
|
|
|
8201
8453
|
// src/class-cache.ts
|
|
8202
|
-
import { existsSync as
|
|
8203
|
-
import { join as
|
|
8454
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
|
|
8455
|
+
import { join as join8 } from "node:path";
|
|
8204
8456
|
function declaredClasses(projectPath) {
|
|
8205
8457
|
const declared = new Map;
|
|
8458
|
+
eachScript(projectPath, (script, source) => {
|
|
8459
|
+
const found = DECLARATION.exec(source);
|
|
8460
|
+
if (found?.[1]) {
|
|
8461
|
+
declared.set(found[1], script);
|
|
8462
|
+
}
|
|
8463
|
+
});
|
|
8464
|
+
return declared;
|
|
8465
|
+
}
|
|
8466
|
+
function eachScript(projectPath, each) {
|
|
8206
8467
|
const visit = (directory, prefix) => {
|
|
8207
|
-
if (
|
|
8468
|
+
if (existsSync7(join8(directory, ".gdignore"))) {
|
|
8208
8469
|
return;
|
|
8209
8470
|
}
|
|
8210
|
-
for (const entry of
|
|
8471
|
+
for (const entry of readdirSync3(directory, { withFileTypes: true })) {
|
|
8211
8472
|
if (entry.name.startsWith(".")) {
|
|
8212
8473
|
continue;
|
|
8213
8474
|
}
|
|
8214
|
-
const path =
|
|
8475
|
+
const path = join8(directory, entry.name);
|
|
8215
8476
|
if (entry.isDirectory()) {
|
|
8216
8477
|
visit(path, `${prefix}${entry.name}/`);
|
|
8217
8478
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
8218
|
-
|
|
8219
|
-
if (found?.[1]) {
|
|
8220
|
-
declared.set(found[1], `res://${prefix}${entry.name}`);
|
|
8221
|
-
}
|
|
8479
|
+
each(`res://${prefix}${entry.name}`, readFileSync5(path, "utf8"));
|
|
8222
8480
|
}
|
|
8223
8481
|
}
|
|
8224
8482
|
};
|
|
8225
8483
|
visit(projectPath, "");
|
|
8226
|
-
|
|
8484
|
+
}
|
|
8485
|
+
function scriptsNaming(projectPath, classes) {
|
|
8486
|
+
const named = new Map(classes.map((name) => [name, ""]));
|
|
8487
|
+
const naming = [];
|
|
8488
|
+
eachScript(projectPath, (script, source) => {
|
|
8489
|
+
const declares = DECLARATION.exec(source)?.[1];
|
|
8490
|
+
if (declares !== undefined && named.has(declares)) {
|
|
8491
|
+
return;
|
|
8492
|
+
}
|
|
8493
|
+
const code = codeOf(source);
|
|
8494
|
+
if (classesNamedIn(code, named).length > 0) {
|
|
8495
|
+
naming.push({ script, tool: /^\s*@tool\b/m.test(code) });
|
|
8496
|
+
}
|
|
8497
|
+
});
|
|
8498
|
+
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
8499
|
+
}
|
|
8500
|
+
function codeOf(source) {
|
|
8501
|
+
let code = "";
|
|
8502
|
+
let at = 0;
|
|
8503
|
+
while (at < source.length) {
|
|
8504
|
+
const here = source[at] ?? "";
|
|
8505
|
+
if (here === "#") {
|
|
8506
|
+
const end = source.indexOf(`
|
|
8507
|
+
`, at);
|
|
8508
|
+
at = end === -1 ? source.length : end;
|
|
8509
|
+
continue;
|
|
8510
|
+
}
|
|
8511
|
+
if (here === '"' || here === "'") {
|
|
8512
|
+
const fence = source.startsWith(here.repeat(3), at) ? here.repeat(3) : here;
|
|
8513
|
+
let end = at + fence.length;
|
|
8514
|
+
while (end < source.length && !source.startsWith(fence, end)) {
|
|
8515
|
+
end += source[end] === "\\" ? 2 : 1;
|
|
8516
|
+
}
|
|
8517
|
+
const literal = source.slice(at, Math.min(end + fence.length, source.length));
|
|
8518
|
+
code += literal.replace(/[^\n]/g, " ");
|
|
8519
|
+
at = end + fence.length;
|
|
8520
|
+
continue;
|
|
8521
|
+
}
|
|
8522
|
+
code += here;
|
|
8523
|
+
at += 1;
|
|
8524
|
+
}
|
|
8525
|
+
return code;
|
|
8227
8526
|
}
|
|
8228
8527
|
function cachedClasses(projectPath) {
|
|
8229
|
-
const cache =
|
|
8230
|
-
if (!
|
|
8528
|
+
const cache = join8(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
8529
|
+
if (!existsSync7(cache)) {
|
|
8231
8530
|
return null;
|
|
8232
8531
|
}
|
|
8233
8532
|
const listed = new Map;
|
|
@@ -8340,7 +8639,7 @@ function uncachedClassNote(types) {
|
|
|
8340
8639
|
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
8640
|
}
|
|
8342
8641
|
function cacheWrittenAt(projectPath) {
|
|
8343
|
-
const cache =
|
|
8642
|
+
const cache = join8(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
8344
8643
|
try {
|
|
8345
8644
|
return statSync2(cache).mtimeMs;
|
|
8346
8645
|
} catch {
|
|
@@ -8354,14 +8653,14 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
8354
8653
|
return false;
|
|
8355
8654
|
}
|
|
8356
8655
|
try {
|
|
8357
|
-
return statSync2(
|
|
8656
|
+
return statSync2(join8(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
8358
8657
|
} catch {
|
|
8359
8658
|
return false;
|
|
8360
8659
|
}
|
|
8361
8660
|
});
|
|
8362
8661
|
}
|
|
8363
8662
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
8364
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !
|
|
8663
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync7(join8(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
8365
8664
|
}
|
|
8366
8665
|
function staleAgainst(cached, projectPath) {
|
|
8367
8666
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -15851,29 +16150,29 @@ function readParams(params, key) {
|
|
|
15851
16150
|
}
|
|
15852
16151
|
|
|
15853
16152
|
// src/setup.ts
|
|
15854
|
-
import { spawnSync
|
|
16153
|
+
import { spawnSync } from "node:child_process";
|
|
15855
16154
|
import { createHash as createHash2 } from "node:crypto";
|
|
15856
16155
|
import {
|
|
15857
16156
|
cpSync,
|
|
15858
|
-
existsSync as
|
|
16157
|
+
existsSync as existsSync8,
|
|
15859
16158
|
mkdirSync as mkdirSync4,
|
|
15860
|
-
readdirSync as
|
|
16159
|
+
readdirSync as readdirSync4,
|
|
15861
16160
|
readFileSync as readFileSync7,
|
|
15862
16161
|
rmdirSync,
|
|
15863
16162
|
rmSync as rmSync4,
|
|
15864
16163
|
statSync as statSync3,
|
|
15865
16164
|
writeFileSync as writeFileSync5
|
|
15866
16165
|
} from "node:fs";
|
|
15867
|
-
import { dirname as dirname4, join as
|
|
16166
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
15868
16167
|
import { fileURLToPath } from "node:url";
|
|
15869
16168
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
15870
16169
|
const hash = createHash2("sha256");
|
|
15871
16170
|
const walk = (directory, relative) => {
|
|
15872
|
-
for (const name of
|
|
16171
|
+
for (const name of readdirSync4(directory).sort()) {
|
|
15873
16172
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
15874
16173
|
continue;
|
|
15875
16174
|
}
|
|
15876
|
-
const path =
|
|
16175
|
+
const path = join9(directory, name);
|
|
15877
16176
|
const inside = `${relative}/${name}`;
|
|
15878
16177
|
if (statSync3(path).isDirectory()) {
|
|
15879
16178
|
walk(path, inside);
|
|
@@ -15887,7 +16186,7 @@ function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
15887
16186
|
}
|
|
15888
16187
|
};
|
|
15889
16188
|
for (const name of EDITOR_PLUGINS) {
|
|
15890
|
-
walk(
|
|
16189
|
+
walk(join9(from, name), name);
|
|
15891
16190
|
}
|
|
15892
16191
|
return hash.digest("hex");
|
|
15893
16192
|
}
|
|
@@ -15902,42 +16201,42 @@ function shippedEditorDigest() {
|
|
|
15902
16201
|
return shippedDigest ?? undefined;
|
|
15903
16202
|
}
|
|
15904
16203
|
function installedEditorDigest(projectPath) {
|
|
15905
|
-
const marker =
|
|
15906
|
-
if (!
|
|
16204
|
+
const marker = join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER);
|
|
16205
|
+
if (!existsSync8(marker)) {
|
|
15907
16206
|
return null;
|
|
15908
16207
|
}
|
|
15909
16208
|
const said = readFileSync7(marker, "utf8").trim();
|
|
15910
16209
|
return said === "" ? null : said;
|
|
15911
16210
|
}
|
|
15912
16211
|
function shippedAddonsDirectory() {
|
|
15913
|
-
return
|
|
16212
|
+
return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
15914
16213
|
}
|
|
15915
16214
|
function shippedOperationsScript() {
|
|
15916
|
-
return
|
|
16215
|
+
return join9(dirname4(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
15917
16216
|
}
|
|
15918
16217
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
15919
16218
|
const installed = [];
|
|
15920
16219
|
for (const name of ADDONS) {
|
|
15921
|
-
const source =
|
|
15922
|
-
if (!
|
|
16220
|
+
const source = join9(from, name);
|
|
16221
|
+
if (!existsSync8(join9(source, name === "gdharness_runtime" ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
15923
16222
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
15924
16223
|
}
|
|
15925
|
-
const target =
|
|
15926
|
-
const replaced =
|
|
16224
|
+
const target = join9(projectPath, "addons", name);
|
|
16225
|
+
const replaced = existsSync8(target);
|
|
15927
16226
|
rmSync4(target, { recursive: true, force: true });
|
|
15928
16227
|
mkdirSync4(dirname4(target), { recursive: true });
|
|
15929
16228
|
cpSync(source, target, { recursive: true });
|
|
15930
|
-
writeFileSync5(
|
|
16229
|
+
writeFileSync5(join9(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
15931
16230
|
`);
|
|
15932
16231
|
installed.push({ name, path: target, replaced });
|
|
15933
16232
|
}
|
|
15934
|
-
writeFileSync5(
|
|
16233
|
+
writeFileSync5(join9(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
15935
16234
|
`);
|
|
15936
16235
|
return installed;
|
|
15937
16236
|
}
|
|
15938
16237
|
function installedAddonVersion(projectPath) {
|
|
15939
|
-
const marker =
|
|
15940
|
-
if (!
|
|
16238
|
+
const marker = join9(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
16239
|
+
if (!existsSync8(marker)) {
|
|
15941
16240
|
return null;
|
|
15942
16241
|
}
|
|
15943
16242
|
const said = readFileSync7(marker, "utf8").trim();
|
|
@@ -15946,14 +16245,14 @@ function installedAddonVersion(projectPath) {
|
|
|
15946
16245
|
function removeAddons(projectPath) {
|
|
15947
16246
|
const removed = [];
|
|
15948
16247
|
for (const name of ADDONS) {
|
|
15949
|
-
const target =
|
|
15950
|
-
if (
|
|
16248
|
+
const target = join9(projectPath, "addons", name);
|
|
16249
|
+
if (existsSync8(target)) {
|
|
15951
16250
|
rmSync4(target, { recursive: true, force: true });
|
|
15952
16251
|
removed.push(target);
|
|
15953
16252
|
}
|
|
15954
16253
|
}
|
|
15955
|
-
const addons =
|
|
15956
|
-
if (removed.length > 0 &&
|
|
16254
|
+
const addons = join9(projectPath, "addons");
|
|
16255
|
+
if (removed.length > 0 && existsSync8(addons) && readdirSync4(addons).length === 0) {
|
|
15957
16256
|
rmdirSync(addons);
|
|
15958
16257
|
removed.push(addons);
|
|
15959
16258
|
}
|
|
@@ -15987,7 +16286,7 @@ function loaderAmong(projectPath, named) {
|
|
|
15987
16286
|
}
|
|
15988
16287
|
let source = null;
|
|
15989
16288
|
try {
|
|
15990
|
-
source = readFileSync7(
|
|
16289
|
+
source = readFileSync7(join9(projectPath, path), "utf8");
|
|
15991
16290
|
} catch {}
|
|
15992
16291
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
15993
16292
|
return { name: entry, path, exists: true };
|
|
@@ -16010,7 +16309,7 @@ function trackedByGit(projectPath, wanted) {
|
|
|
16010
16309
|
if (wanted.length === 0) {
|
|
16011
16310
|
return new Set;
|
|
16012
16311
|
}
|
|
16013
|
-
const asked =
|
|
16312
|
+
const asked = spawnSync("git", ["ls-files", "-z", "--", ...wanted], {
|
|
16014
16313
|
cwd: projectPath,
|
|
16015
16314
|
encoding: "utf8"
|
|
16016
16315
|
});
|
|
@@ -16029,10 +16328,10 @@ function enabledPlugins(settings) {
|
|
|
16029
16328
|
function inspectProject(projectPath) {
|
|
16030
16329
|
const problems = [];
|
|
16031
16330
|
const addons = ADDONS.map((name) => {
|
|
16032
|
-
const directory =
|
|
16033
|
-
const marker =
|
|
16034
|
-
const installed =
|
|
16035
|
-
const version =
|
|
16331
|
+
const directory = join9(projectPath, "addons", name);
|
|
16332
|
+
const marker = join9(directory, VERSION_MARKER);
|
|
16333
|
+
const installed = existsSync8(directory);
|
|
16334
|
+
const version = existsSync8(marker) ? readFileSync7(marker, "utf8").trim() : null;
|
|
16036
16335
|
const current = version === SERVER_VERSION;
|
|
16037
16336
|
if (!installed) {
|
|
16038
16337
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -16041,7 +16340,7 @@ function inspectProject(projectPath) {
|
|
|
16041
16340
|
}
|
|
16042
16341
|
return { name, installed, version, current };
|
|
16043
16342
|
});
|
|
16044
|
-
const settings = parseProjectGodot(readFileSync7(
|
|
16343
|
+
const settings = parseProjectGodot(readFileSync7(join9(projectPath, "project.godot"), "utf8"));
|
|
16045
16344
|
const pluginsEnabled = enabledPlugins(settings);
|
|
16046
16345
|
for (const name of EDITOR_PLUGINS) {
|
|
16047
16346
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -16057,7 +16356,7 @@ function inspectProject(projectPath) {
|
|
|
16057
16356
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
16058
16357
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
16059
16358
|
for (const [name, path] of named) {
|
|
16060
|
-
if (!
|
|
16359
|
+
if (!existsSync8(join9(projectPath, path))) {
|
|
16061
16360
|
if (!addonMissing(path)) {
|
|
16062
16361
|
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
16362
|
}
|
|
@@ -16816,7 +17115,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16816
17115
|
name: "editor_run",
|
|
16817
17116
|
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
17117
|
parameters: {
|
|
16819
|
-
projectPath: {
|
|
17118
|
+
projectPath: {
|
|
17119
|
+
...PROJECT_PATH,
|
|
17120
|
+
ops: ["start", "check", "stop", "wait"],
|
|
17121
|
+
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."
|
|
17122
|
+
},
|
|
16820
17123
|
scene: {
|
|
16821
17124
|
type: "string",
|
|
16822
17125
|
ops: ["start", "check"],
|
|
@@ -16948,7 +17251,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16948
17251
|
},
|
|
16949
17252
|
{
|
|
16950
17253
|
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.",
|
|
17254
|
+
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
17255
|
parameters: {
|
|
16953
17256
|
projectPath: PROJECT_PATH,
|
|
16954
17257
|
timeoutMs: {
|
|
@@ -17054,7 +17357,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17054
17357
|
says: {
|
|
17055
17358
|
type: "string",
|
|
17056
17359
|
ops: ["find"],
|
|
17057
|
-
description:
|
|
17360
|
+
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
17361
|
},
|
|
17059
17362
|
limit: {
|
|
17060
17363
|
type: "integer",
|
|
@@ -17091,7 +17394,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17091
17394
|
requires: []
|
|
17092
17395
|
},
|
|
17093
17396
|
rect: {
|
|
17094
|
-
summary: "one node's rectangle or position, in canvas and in window pixels. A 3D node answers with the point to aim at, which is the middle of what it draws rather than the origin it stands on, the rectangle it covers under covers, the camera that drew it, and behind_camera when it is not in front of one",
|
|
17397
|
+
summary: "one node's rectangle or position, in canvas and in window pixels. Canvas is the node's own viewport; window is where it is drawn in the game's window, through any embedded dialog or SubViewport it sits in, which is what runtime_input mouse_click and mouse_motion take. A 3D node answers with the point to aim at, which is the middle of what it draws rather than the origin it stands on, the rectangle it covers under covers, the camera that drew it, and behind_camera when it is not in front of one",
|
|
17095
17398
|
requires: ["nodePath"]
|
|
17096
17399
|
},
|
|
17097
17400
|
property: {
|
|
@@ -17209,7 +17512,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17209
17512
|
says: {
|
|
17210
17513
|
type: "string",
|
|
17211
17514
|
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."
|
|
17515
|
+
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
17516
|
},
|
|
17214
17517
|
action: {
|
|
17215
17518
|
type: "string",
|
|
@@ -17290,7 +17593,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17290
17593
|
requires: []
|
|
17291
17594
|
},
|
|
17292
17595
|
choose: {
|
|
17293
|
-
summary: "take an item out of a menu, by what it says or by where it is in the list. A menu's items are drawn rather than built, so there is nothing to click: the item takes the focus and Enter presses it, which is the engine's own path and needs no window. Answers with what was chosen and what the button in front of it shows now",
|
|
17596
|
+
summary: "take an item out of a menu, by what it says or by where it is in the list. A menu's items are drawn rather than built, so there is nothing to click: the item takes the focus and Enter presses it, which is the engine's own path and needs no window. Answers with what was chosen, read before the press, what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick), and what the button in front of it shows now when it is still there",
|
|
17294
17597
|
requires: ["nodePath"]
|
|
17295
17598
|
},
|
|
17296
17599
|
action: { summary: "press an action, or hold it", requires: ["action"] },
|
|
@@ -17340,7 +17643,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17340
17643
|
says: {
|
|
17341
17644
|
type: "string",
|
|
17342
17645
|
ops: ["until"],
|
|
17343
|
-
description:
|
|
17646
|
+
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
17647
|
},
|
|
17345
17648
|
includeHidden: {
|
|
17346
17649
|
type: "boolean",
|
|
@@ -32099,10 +32402,10 @@ var init_stdio = __esm(() => {
|
|
|
32099
32402
|
});
|
|
32100
32403
|
|
|
32101
32404
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
32102
|
-
import
|
|
32405
|
+
import process9 from "node:process";
|
|
32103
32406
|
|
|
32104
32407
|
class StdioServerTransport {
|
|
32105
|
-
constructor(_stdin =
|
|
32408
|
+
constructor(_stdin = process9.stdin, _stdout = process9.stdout, options) {
|
|
32106
32409
|
this._stdin = _stdin;
|
|
32107
32410
|
this._stdout = _stdout;
|
|
32108
32411
|
this._started = false;
|
|
@@ -32167,10 +32470,10 @@ var init_stdio2 = __esm(() => {
|
|
|
32167
32470
|
});
|
|
32168
32471
|
|
|
32169
32472
|
// src/runtime-client.ts
|
|
32170
|
-
import { existsSync as
|
|
32473
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync4, unlinkSync } from "node:fs";
|
|
32171
32474
|
import { createConnection } from "node:net";
|
|
32172
32475
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
32173
|
-
import { dirname as dirname7, join as
|
|
32476
|
+
import { dirname as dirname7, join as join12, resolve as resolve3 } from "node:path";
|
|
32174
32477
|
import { setTimeout as delay } from "node:timers/promises";
|
|
32175
32478
|
function runtimeDirectory(variables = process.env) {
|
|
32176
32479
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -32178,20 +32481,20 @@ function runtimeDirectory(variables = process.env) {
|
|
|
32178
32481
|
return explicit;
|
|
32179
32482
|
}
|
|
32180
32483
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
32181
|
-
return
|
|
32484
|
+
return join12(perUser ?? tmpdir3(), "gdharness");
|
|
32182
32485
|
}
|
|
32183
32486
|
function runtimeDirectories(variables = process.env) {
|
|
32184
32487
|
const candidates = [runtimeDirectory(variables)];
|
|
32185
32488
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
32186
32489
|
for (const base of fallbacks) {
|
|
32187
|
-
candidates.push(
|
|
32490
|
+
candidates.push(join12(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
32188
32491
|
}
|
|
32189
32492
|
return [...new Set(candidates.map((path) => resolve3(path)))];
|
|
32190
32493
|
}
|
|
32191
32494
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
32192
32495
|
for (const directory of directories) {
|
|
32193
|
-
const path =
|
|
32194
|
-
if (
|
|
32496
|
+
const path = join12(directory, `runtime-${pid}.log`);
|
|
32497
|
+
if (existsSync10(path)) {
|
|
32195
32498
|
return path;
|
|
32196
32499
|
}
|
|
32197
32500
|
}
|
|
@@ -32219,14 +32522,6 @@ function reportAlreadyInTranscript(transcript, report) {
|
|
|
32219
32522
|
const end = boundary + copied.length;
|
|
32220
32523
|
return end <= report.length && report.subarray(boundary, end).equals(copied) ? end : report.length;
|
|
32221
32524
|
}
|
|
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
32525
|
function parseAnnouncement(file, pid) {
|
|
32231
32526
|
let fields;
|
|
32232
32527
|
try {
|
|
@@ -32314,46 +32609,52 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
32314
32609
|
const known = verdicts.get(one.file);
|
|
32315
32610
|
return known === undefined || known.writtenAt !== one.writtenAt || !known.stranger && now - known.judgedAt > CONFIRMED_FOR_MS;
|
|
32316
32611
|
});
|
|
32317
|
-
if (toJudge.length > 0) {
|
|
32318
|
-
|
|
32319
|
-
|
|
32320
|
-
|
|
32321
|
-
|
|
32322
|
-
|
|
32323
|
-
|
|
32612
|
+
if (toJudge.length > 0 && judging === null) {
|
|
32613
|
+
judging = startTimes(toJudge.map((one) => one.pid)).then((began) => {
|
|
32614
|
+
for (const one of toJudge) {
|
|
32615
|
+
const at = began.get(one.pid);
|
|
32616
|
+
const stranger = at !== undefined && at - one.writtenAt > STARTED_AFTER_ANNOUNCING_MS;
|
|
32617
|
+
verdicts.set(one.file, { writtenAt: one.writtenAt, judgedAt: now, stranger });
|
|
32618
|
+
}
|
|
32619
|
+
}).catch(() => {}).finally(() => {
|
|
32620
|
+
judging = null;
|
|
32621
|
+
});
|
|
32324
32622
|
}
|
|
32325
|
-
return new Set(candidates.filter((one) =>
|
|
32623
|
+
return new Set(candidates.filter((one) => {
|
|
32624
|
+
const known = verdicts.get(one.file);
|
|
32625
|
+
return known?.stranger === true && known.writtenAt === one.writtenAt;
|
|
32626
|
+
}).map((one) => one.file));
|
|
32326
32627
|
}
|
|
32327
32628
|
function announcedIn(directory) {
|
|
32328
|
-
if (!
|
|
32629
|
+
if (!existsSync10(directory)) {
|
|
32329
32630
|
return [];
|
|
32330
32631
|
}
|
|
32331
32632
|
const found = [];
|
|
32332
32633
|
const candidates = [];
|
|
32333
32634
|
const entries = [];
|
|
32334
|
-
for (const entry of
|
|
32635
|
+
for (const entry of readdirSync6(directory)) {
|
|
32335
32636
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
32336
32637
|
if (report) {
|
|
32337
|
-
sweepErrorReport(
|
|
32638
|
+
sweepErrorReport(join12(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
32338
32639
|
continue;
|
|
32339
32640
|
}
|
|
32340
32641
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
32341
32642
|
if (!match) {
|
|
32342
32643
|
continue;
|
|
32343
32644
|
}
|
|
32344
|
-
const file =
|
|
32645
|
+
const file = join12(directory, entry);
|
|
32345
32646
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
32346
|
-
const
|
|
32347
|
-
entries.push({ file, pid,
|
|
32348
|
-
if (
|
|
32647
|
+
const running = alive(pid);
|
|
32648
|
+
entries.push({ file, pid, running });
|
|
32649
|
+
if (running) {
|
|
32349
32650
|
try {
|
|
32350
32651
|
candidates.push({ file, pid, writtenAt: statSync4(file).mtimeMs });
|
|
32351
32652
|
} catch {}
|
|
32352
32653
|
}
|
|
32353
32654
|
}
|
|
32354
32655
|
const strangers = strangersAmong(candidates, Date.now());
|
|
32355
|
-
for (const { file, pid,
|
|
32356
|
-
const gone = !
|
|
32656
|
+
for (const { file, pid, running } of entries) {
|
|
32657
|
+
const gone = !running || strangers.has(file);
|
|
32357
32658
|
const announced = gone ? { kind: "rubbish" } : parseAnnouncement(file, pid);
|
|
32358
32659
|
if (announced.kind === "rubbish") {
|
|
32359
32660
|
if (!gone) {
|
|
@@ -32369,7 +32670,7 @@ function announcedIn(directory) {
|
|
|
32369
32670
|
found.push(announced);
|
|
32370
32671
|
}
|
|
32371
32672
|
const held = new Set(entries.map((one) => one.file));
|
|
32372
|
-
const here =
|
|
32673
|
+
const here = join12(directory, ".");
|
|
32373
32674
|
for (const file of verdicts.keys()) {
|
|
32374
32675
|
if (dirname7(file) === here && !held.has(file)) {
|
|
32375
32676
|
verdicts.delete(file);
|
|
@@ -32378,13 +32679,13 @@ function announcedIn(directory) {
|
|
|
32378
32679
|
return found;
|
|
32379
32680
|
}
|
|
32380
32681
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
32381
|
-
const files = directories.map((directory) =>
|
|
32682
|
+
const files = directories.map((directory) => join12(directory, `runtime-${pid}.json`)).filter((file) => existsSync10(file));
|
|
32382
32683
|
const first = files[0];
|
|
32383
32684
|
if (first === undefined) {
|
|
32384
32685
|
return;
|
|
32385
32686
|
}
|
|
32386
32687
|
const until = Date.now() + ENDED_WITHIN_MS;
|
|
32387
|
-
while (
|
|
32688
|
+
while (alive(pid)) {
|
|
32388
32689
|
const left = until - Date.now();
|
|
32389
32690
|
if (left <= 0) {
|
|
32390
32691
|
return;
|
|
@@ -32400,7 +32701,7 @@ async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
|
32400
32701
|
}
|
|
32401
32702
|
}
|
|
32402
32703
|
function sweepErrorReport(path, pid, now = Date.now()) {
|
|
32403
|
-
if (
|
|
32704
|
+
if (alive(pid)) {
|
|
32404
32705
|
return;
|
|
32405
32706
|
}
|
|
32406
32707
|
try {
|
|
@@ -32663,8 +32964,9 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
32663
32964
|
});
|
|
32664
32965
|
});
|
|
32665
32966
|
}
|
|
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;
|
|
32967
|
+
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
32968
|
var init_runtime_client = __esm(() => {
|
|
32969
|
+
init_alive();
|
|
32668
32970
|
init_paths();
|
|
32669
32971
|
init_process_children();
|
|
32670
32972
|
ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
|
|
@@ -32678,9 +32980,9 @@ var init_runtime_client = __esm(() => {
|
|
|
32678
32980
|
|
|
32679
32981
|
// src/boot-note.ts
|
|
32680
32982
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
32681
|
-
import { dirname as dirname8, join as
|
|
32983
|
+
import { dirname as dirname8, join as join13 } from "node:path";
|
|
32682
32984
|
function bootNotePath(projectPath) {
|
|
32683
|
-
return
|
|
32985
|
+
return join13(projectPath, ".godot", "gdharness-boot.json");
|
|
32684
32986
|
}
|
|
32685
32987
|
function readBootNote(projectPath) {
|
|
32686
32988
|
try {
|
|
@@ -32713,9 +33015,9 @@ var init_boot_note = __esm(() => {
|
|
|
32713
33015
|
|
|
32714
33016
|
// src/breakpoint-note.ts
|
|
32715
33017
|
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
|
|
33018
|
+
import { dirname as dirname9, isAbsolute as isAbsolute2, join as join14, relative as relative2 } from "node:path";
|
|
32717
33019
|
function breakpointNotePath(projectPath) {
|
|
32718
|
-
return
|
|
33020
|
+
return join14(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
32719
33021
|
}
|
|
32720
33022
|
function writeBreakpointNote(projectPath, held) {
|
|
32721
33023
|
const path = breakpointNotePath(projectPath);
|
|
@@ -32756,7 +33058,7 @@ function readBreakpointNote(projectPath) {
|
|
|
32756
33058
|
}
|
|
32757
33059
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
32758
33060
|
if (lines.length > 0) {
|
|
32759
|
-
held.push({ scriptPath: realPathOr(
|
|
33061
|
+
held.push({ scriptPath: realPathOr(join14(projectPath, script)), lines });
|
|
32760
33062
|
}
|
|
32761
33063
|
}
|
|
32762
33064
|
return held;
|
|
@@ -32775,9 +33077,9 @@ var init_breakpoint_note = __esm(() => {
|
|
|
32775
33077
|
|
|
32776
33078
|
// src/class-note.ts
|
|
32777
33079
|
import { mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
|
|
32778
|
-
import { dirname as dirname10, join as
|
|
33080
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
32779
33081
|
function classNotePath(projectPath) {
|
|
32780
|
-
return
|
|
33082
|
+
return join15(projectPath, ".godot", "gdharness-classes.json");
|
|
32781
33083
|
}
|
|
32782
33084
|
function readClassNote(projectPath) {
|
|
32783
33085
|
try {
|
|
@@ -32806,11 +33108,11 @@ function writeClassNote(projectPath, note) {
|
|
|
32806
33108
|
var init_class_note = () => {};
|
|
32807
33109
|
|
|
32808
33110
|
// src/config-pin.ts
|
|
32809
|
-
import { existsSync as
|
|
32810
|
-
import { join as
|
|
33111
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
|
|
33112
|
+
import { join as join16 } from "node:path";
|
|
32811
33113
|
function versionsNamedInConfig(projectPath) {
|
|
32812
|
-
const config =
|
|
32813
|
-
if (!
|
|
33114
|
+
const config = join16(projectPath, ".mcp.json");
|
|
33115
|
+
if (!existsSync11(config)) {
|
|
32814
33116
|
return [];
|
|
32815
33117
|
}
|
|
32816
33118
|
let text;
|
|
@@ -33443,6 +33745,9 @@ class GodotDAPClient {
|
|
|
33443
33745
|
isConnected() {
|
|
33444
33746
|
return this.connected;
|
|
33445
33747
|
}
|
|
33748
|
+
connection() {
|
|
33749
|
+
return this.connected ? this.socket : null;
|
|
33750
|
+
}
|
|
33446
33751
|
isStopped() {
|
|
33447
33752
|
return this.halt !== null;
|
|
33448
33753
|
}
|
|
@@ -33572,7 +33877,7 @@ var init_desktop = () => {};
|
|
|
33572
33877
|
// src/editor-log.ts
|
|
33573
33878
|
import {
|
|
33574
33879
|
closeSync,
|
|
33575
|
-
existsSync as
|
|
33880
|
+
existsSync as existsSync12,
|
|
33576
33881
|
mkdirSync as mkdirSync9,
|
|
33577
33882
|
openSync,
|
|
33578
33883
|
readFileSync as readFileSync14,
|
|
@@ -33581,12 +33886,12 @@ import {
|
|
|
33581
33886
|
statSync as statSync5,
|
|
33582
33887
|
writeFileSync as writeFileSync10
|
|
33583
33888
|
} from "node:fs";
|
|
33584
|
-
import { dirname as dirname11, join as
|
|
33889
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
33585
33890
|
function editorLogPath(projectPath) {
|
|
33586
|
-
return
|
|
33891
|
+
return join17(projectPath, ".godot", "gdharness-editor.log");
|
|
33587
33892
|
}
|
|
33588
33893
|
function editorLogNotePath(projectPath) {
|
|
33589
|
-
return
|
|
33894
|
+
return join17(projectPath, ".godot", "gdharness-editor.json");
|
|
33590
33895
|
}
|
|
33591
33896
|
function readEditorLogNote(projectPath) {
|
|
33592
33897
|
try {
|
|
@@ -33631,7 +33936,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
33631
33936
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
33632
33937
|
}
|
|
33633
33938
|
const path = editorLogPath(projectPath);
|
|
33634
|
-
if (!
|
|
33939
|
+
if (!existsSync12(path)) {
|
|
33635
33940
|
return { kind: "not written" };
|
|
33636
33941
|
}
|
|
33637
33942
|
const log = new GameLog;
|
|
@@ -38060,7 +38365,7 @@ var init_lsp_client = __esm(() => {
|
|
|
38060
38365
|
});
|
|
38061
38366
|
|
|
38062
38367
|
// src/outside.ts
|
|
38063
|
-
import { spawn } from "node:child_process";
|
|
38368
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
38064
38369
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
38065
38370
|
function changesFrom(env, base) {
|
|
38066
38371
|
const changes = {};
|
|
@@ -38093,7 +38398,7 @@ function readLaunched(printed) {
|
|
|
38093
38398
|
}
|
|
38094
38399
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
38095
38400
|
return await new Promise((resolve) => {
|
|
38096
|
-
const launcher =
|
|
38401
|
+
const launcher = spawn2(runtime, [keeper, "launch"], {
|
|
38097
38402
|
stdio: ["pipe", "pipe", "pipe"],
|
|
38098
38403
|
windowsHide: true
|
|
38099
38404
|
});
|
|
@@ -38132,14 +38437,14 @@ import { execFile as execFile4 } from "node:child_process";
|
|
|
38132
38437
|
import { promisify as promisify4 } from "node:util";
|
|
38133
38438
|
async function cpuSecondsOf(pid) {
|
|
38134
38439
|
try {
|
|
38135
|
-
return process.platform === "win32" ? await
|
|
38440
|
+
return process.platform === "win32" ? await secondsOnWindows(pid) : await askPosix2(pid);
|
|
38136
38441
|
} catch {
|
|
38137
38442
|
return;
|
|
38138
38443
|
}
|
|
38139
38444
|
}
|
|
38140
|
-
async function
|
|
38141
|
-
const
|
|
38142
|
-
const seconds = Number(
|
|
38445
|
+
async function secondsOnWindows(pid) {
|
|
38446
|
+
const said = await askWindows(`(Get-Process -Id ${pid}).CPU`, ASK_TIMEOUT_MS2);
|
|
38447
|
+
const seconds = Number(said.trim().replace(",", "."));
|
|
38143
38448
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
38144
38449
|
}
|
|
38145
38450
|
async function askPosix2(pid) {
|
|
@@ -38163,17 +38468,18 @@ function secondsFromClock(printed) {
|
|
|
38163
38468
|
}
|
|
38164
38469
|
var run4, ASK_TIMEOUT_MS2 = 6000;
|
|
38165
38470
|
var init_process_time = __esm(() => {
|
|
38471
|
+
init_windows_ask();
|
|
38166
38472
|
run4 = promisify4(execFile4);
|
|
38167
38473
|
});
|
|
38168
38474
|
|
|
38169
38475
|
// src/project-scan.ts
|
|
38170
|
-
import { existsSync as
|
|
38171
|
-
import { join as
|
|
38476
|
+
import { existsSync as existsSync13, readdirSync as readdirSync7, readFileSync as readFileSync15 } from "node:fs";
|
|
38477
|
+
import { join as join18 } from "node:path";
|
|
38172
38478
|
function skipped(name) {
|
|
38173
38479
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
38174
38480
|
}
|
|
38175
38481
|
function steppedOver(directory) {
|
|
38176
|
-
return
|
|
38482
|
+
return existsSync13(join18(directory, ".gdignore"));
|
|
38177
38483
|
}
|
|
38178
38484
|
function projectStructure(projectPath) {
|
|
38179
38485
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -38181,12 +38487,12 @@ function projectStructure(projectPath) {
|
|
|
38181
38487
|
if (steppedOver(directory)) {
|
|
38182
38488
|
return;
|
|
38183
38489
|
}
|
|
38184
|
-
for (const entry of
|
|
38490
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
38185
38491
|
if (skipped(entry.name)) {
|
|
38186
38492
|
continue;
|
|
38187
38493
|
}
|
|
38188
38494
|
if (entry.isDirectory()) {
|
|
38189
|
-
visit(
|
|
38495
|
+
visit(join18(directory, entry.name));
|
|
38190
38496
|
} else if (entry.isFile()) {
|
|
38191
38497
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
38192
38498
|
if (extension === "tscn") {
|
|
@@ -38210,20 +38516,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
38210
38516
|
if (steppedOver(directory)) {
|
|
38211
38517
|
return;
|
|
38212
38518
|
}
|
|
38213
|
-
for (const entry of
|
|
38519
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
38214
38520
|
if (skipped(entry.name)) {
|
|
38215
38521
|
continue;
|
|
38216
38522
|
}
|
|
38217
38523
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
38218
38524
|
if (entry.isDirectory()) {
|
|
38219
|
-
visit(
|
|
38525
|
+
visit(join18(directory, entry.name), spelled);
|
|
38220
38526
|
continue;
|
|
38221
38527
|
}
|
|
38222
38528
|
if (!entry.isFile()) {
|
|
38223
38529
|
continue;
|
|
38224
38530
|
}
|
|
38225
38531
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
38226
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
38532
|
+
if (CARRIES_A_UID.has(extension) && !existsSync13(join18(directory, `${entry.name}.uid`))) {
|
|
38227
38533
|
found.push(spelled);
|
|
38228
38534
|
}
|
|
38229
38535
|
}
|
|
@@ -38251,14 +38557,14 @@ function searchProject(projectPath, options) {
|
|
|
38251
38557
|
if (steppedOver(directory)) {
|
|
38252
38558
|
return;
|
|
38253
38559
|
}
|
|
38254
|
-
for (const entry of
|
|
38560
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
38255
38561
|
if (full()) {
|
|
38256
38562
|
return;
|
|
38257
38563
|
}
|
|
38258
38564
|
if (skipped(entry.name)) {
|
|
38259
38565
|
continue;
|
|
38260
38566
|
}
|
|
38261
|
-
const entryPath =
|
|
38567
|
+
const entryPath = join18(directory, entry.name);
|
|
38262
38568
|
if (entry.isDirectory()) {
|
|
38263
38569
|
visit(entryPath);
|
|
38264
38570
|
continue;
|
|
@@ -38299,12 +38605,12 @@ var init_project_scan = __esm(() => {
|
|
|
38299
38605
|
|
|
38300
38606
|
// src/reimport.ts
|
|
38301
38607
|
import { createHash as createHash3 } from "node:crypto";
|
|
38302
|
-
import { existsSync as
|
|
38303
|
-
import { basename as basename3, join as
|
|
38608
|
+
import { existsSync as existsSync14, readFileSync as readFileSync16, rmSync as rmSync8, statSync as statSync6, utimesSync, writeFileSync as writeFileSync11 } from "node:fs";
|
|
38609
|
+
import { basename as basename3, join as join19 } from "node:path";
|
|
38304
38610
|
function forceNextImport(projectPath, resourcePath) {
|
|
38305
38611
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
38306
|
-
const sidecar =
|
|
38307
|
-
if (!
|
|
38612
|
+
const sidecar = join19(projectPath, `${relative}.import`);
|
|
38613
|
+
if (!existsSync14(sidecar)) {
|
|
38308
38614
|
return;
|
|
38309
38615
|
}
|
|
38310
38616
|
const { atime, mtimeMs } = statSync6(sidecar);
|
|
@@ -38314,15 +38620,15 @@ function forceNextImport(projectPath, resourcePath) {
|
|
|
38314
38620
|
}
|
|
38315
38621
|
function importRecord(projectPath, resourcePath) {
|
|
38316
38622
|
const digest = createHash3("md5").update(resourcePath).digest("hex");
|
|
38317
|
-
return
|
|
38623
|
+
return join19(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
|
|
38318
38624
|
}
|
|
38319
38625
|
var init_reimport = () => {};
|
|
38320
38626
|
|
|
38321
38627
|
// src/restart-note.ts
|
|
38322
38628
|
import { mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
|
38323
|
-
import { dirname as dirname13, join as
|
|
38629
|
+
import { dirname as dirname13, join as join20 } from "node:path";
|
|
38324
38630
|
function restartNotePath(projectPath) {
|
|
38325
|
-
return
|
|
38631
|
+
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
38326
38632
|
}
|
|
38327
38633
|
function noteRestartBegun(note) {
|
|
38328
38634
|
const path = restartNotePath(note.projectPath);
|
|
@@ -38361,28 +38667,29 @@ var init_restart_note = __esm(() => {
|
|
|
38361
38667
|
});
|
|
38362
38668
|
|
|
38363
38669
|
// src/run-record.ts
|
|
38364
|
-
import {
|
|
38670
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
38365
38671
|
import { createHash as createHash4 } from "node:crypto";
|
|
38366
38672
|
import {
|
|
38367
38673
|
mkdirSync as mkdirSync11,
|
|
38368
38674
|
openSync as openSync2,
|
|
38369
|
-
readdirSync as
|
|
38675
|
+
readdirSync as readdirSync8,
|
|
38370
38676
|
readFileSync as readFileSync18,
|
|
38371
38677
|
realpathSync as realpathSync3,
|
|
38372
38678
|
rmSync as rmSync10,
|
|
38373
38679
|
statSync as statSync7,
|
|
38374
38680
|
writeFileSync as writeFileSync13
|
|
38375
38681
|
} from "node:fs";
|
|
38376
|
-
import { basename as basename4, dirname as dirname14, join as
|
|
38682
|
+
import { basename as basename4, dirname as dirname14, join as join21, resolve as resolve5 } from "node:path";
|
|
38683
|
+
import { promisify as promisify5 } from "node:util";
|
|
38377
38684
|
function runsDirectory() {
|
|
38378
|
-
return
|
|
38685
|
+
return join21(runtimeDirectory(), "runs");
|
|
38379
38686
|
}
|
|
38380
38687
|
function realAsFarAsItExists(path) {
|
|
38381
38688
|
const missing = [];
|
|
38382
38689
|
let at = path;
|
|
38383
38690
|
for (;; ) {
|
|
38384
38691
|
try {
|
|
38385
|
-
return
|
|
38692
|
+
return join21(realpathSync3.native(at), ...missing);
|
|
38386
38693
|
} catch {
|
|
38387
38694
|
const parent = dirname14(at);
|
|
38388
38695
|
if (parent === at) {
|
|
@@ -38399,15 +38706,15 @@ function projectKey(projectPath) {
|
|
|
38399
38706
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
38400
38707
|
}
|
|
38401
38708
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
38402
|
-
return
|
|
38709
|
+
return join21(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
38403
38710
|
}
|
|
38404
38711
|
function openTranscript(startedAt) {
|
|
38405
38712
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
38406
|
-
const path =
|
|
38713
|
+
const path = join21(runsDirectory(), `run-${startedAt}.log`);
|
|
38407
38714
|
return { path, fd: openSync2(path, "a") };
|
|
38408
38715
|
}
|
|
38409
38716
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
38410
|
-
return
|
|
38717
|
+
return join21(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
38411
38718
|
}
|
|
38412
38719
|
function writeEditorRunNote(note) {
|
|
38413
38720
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
@@ -38415,7 +38722,7 @@ function writeEditorRunNote(note) {
|
|
|
38415
38722
|
}
|
|
38416
38723
|
function readEditorRunNote(projectPath) {
|
|
38417
38724
|
for (const directory of runtimeDirectories()) {
|
|
38418
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
38725
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join21(directory, "runs", "editor-run.json"));
|
|
38419
38726
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
38420
38727
|
return note;
|
|
38421
38728
|
}
|
|
@@ -38447,7 +38754,7 @@ function readRunRecord(projectPath) {
|
|
|
38447
38754
|
if (own !== null) {
|
|
38448
38755
|
return own;
|
|
38449
38756
|
}
|
|
38450
|
-
const shared = recordAt(
|
|
38757
|
+
const shared = recordAt(join21(directory, "runs", SHARED_NOTE));
|
|
38451
38758
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
38452
38759
|
return shared;
|
|
38453
38760
|
}
|
|
@@ -38459,13 +38766,13 @@ function everyRunRecord() {
|
|
|
38459
38766
|
for (const directory of runtimeDirectories()) {
|
|
38460
38767
|
let entries;
|
|
38461
38768
|
try {
|
|
38462
|
-
entries =
|
|
38769
|
+
entries = readdirSync8(join21(directory, "runs"));
|
|
38463
38770
|
} catch {
|
|
38464
38771
|
continue;
|
|
38465
38772
|
}
|
|
38466
38773
|
for (const entry of entries) {
|
|
38467
38774
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
38468
|
-
const record = recordAt(
|
|
38775
|
+
const record = recordAt(join21(directory, "runs", entry));
|
|
38469
38776
|
if (record !== null) {
|
|
38470
38777
|
found.push(record);
|
|
38471
38778
|
}
|
|
@@ -38503,49 +38810,47 @@ function recordAt(path) {
|
|
|
38503
38810
|
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {}
|
|
38504
38811
|
};
|
|
38505
38812
|
}
|
|
38506
|
-
function runningAs(pid) {
|
|
38813
|
+
async function runningAs(pid) {
|
|
38507
38814
|
if (process.platform === "win32") {
|
|
38508
|
-
const answer = windowsCommandLine(pid);
|
|
38815
|
+
const answer = await windowsCommandLine(pid);
|
|
38509
38816
|
if (answer !== null) {
|
|
38510
38817
|
const [, ...rest] = answer.split(`
|
|
38511
38818
|
`);
|
|
38512
38819
|
const line = rest.join(`
|
|
38513
38820
|
`).trim();
|
|
38514
|
-
const began = startedAt(pid, answer);
|
|
38821
|
+
const began = await startedAt(pid, answer);
|
|
38515
38822
|
if (line !== "") {
|
|
38516
38823
|
return { kind: "commandLine", text: line, ...began === null ? {} : { startedAt: began } };
|
|
38517
38824
|
}
|
|
38518
38825
|
}
|
|
38519
|
-
const image = windowsImage(pid);
|
|
38826
|
+
const image = await windowsImage(pid);
|
|
38520
38827
|
return image === null ? null : { kind: "image", text: image };
|
|
38521
38828
|
}
|
|
38522
38829
|
try {
|
|
38523
|
-
const began = startedAt(pid, null);
|
|
38830
|
+
const began = await startedAt(pid, null);
|
|
38524
38831
|
const when = began === null ? {} : { startedAt: began };
|
|
38525
38832
|
if (process.platform === "linux") {
|
|
38526
38833
|
const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
38527
38834
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
38528
38835
|
}
|
|
38529
|
-
const args =
|
|
38530
|
-
encoding: "utf8",
|
|
38531
|
-
timeout: 15000
|
|
38532
|
-
}).trim();
|
|
38836
|
+
const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
38533
38837
|
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
38534
38838
|
} catch {
|
|
38535
38839
|
return null;
|
|
38536
38840
|
}
|
|
38537
38841
|
}
|
|
38538
|
-
function listeningPid(port) {
|
|
38842
|
+
async function listeningPid(port) {
|
|
38539
38843
|
try {
|
|
38540
38844
|
if (process.platform === "win32") {
|
|
38541
|
-
|
|
38845
|
+
const { stdout } = await execFileAsync("netstat", ["-ano"], { encoding: "utf8", timeout: 15000 });
|
|
38846
|
+
return listeningPidInNetstat(stdout, port);
|
|
38542
38847
|
}
|
|
38543
38848
|
for (const [command, args, pattern] of [
|
|
38544
38849
|
["lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], /^(\d+)/],
|
|
38545
38850
|
["ss", ["-ltnpH", `sport = :${port}`], /pid=(\d+)/]
|
|
38546
38851
|
]) {
|
|
38547
38852
|
try {
|
|
38548
|
-
const said =
|
|
38853
|
+
const said = (await execFileAsync(command, [...args], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
38549
38854
|
const found = pattern.exec(said);
|
|
38550
38855
|
if (found?.[1] !== undefined) {
|
|
38551
38856
|
return Number(found[1]);
|
|
@@ -38570,39 +38875,31 @@ function listeningPidInNetstat(printed, port) {
|
|
|
38570
38875
|
}
|
|
38571
38876
|
return null;
|
|
38572
38877
|
}
|
|
38573
|
-
function windowsCommandLine(pid) {
|
|
38878
|
+
async function windowsCommandLine(pid) {
|
|
38574
38879
|
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();
|
|
38880
|
+
const answer = (await askWindows(`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"; if ($p) { $p.CreationDate.ToUniversalTime().ToString("o"); $p.CommandLine }`, 15000)).trim();
|
|
38581
38881
|
return answer === "" ? null : answer;
|
|
38582
38882
|
} catch {
|
|
38583
38883
|
return null;
|
|
38584
38884
|
}
|
|
38585
38885
|
}
|
|
38586
|
-
function startedAt(pid, windowsAnswer) {
|
|
38886
|
+
async function startedAt(pid, windowsAnswer) {
|
|
38587
38887
|
if (process.platform === "win32") {
|
|
38588
38888
|
const when = Date.parse(windowsAnswer?.split(`
|
|
38589
38889
|
`)[0]?.trim() ?? "");
|
|
38590
38890
|
return Number.isNaN(when) ? null : when;
|
|
38591
38891
|
}
|
|
38592
38892
|
try {
|
|
38593
|
-
const said =
|
|
38594
|
-
encoding: "utf8",
|
|
38595
|
-
timeout: 15000
|
|
38596
|
-
}).trim();
|
|
38893
|
+
const said = (await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
38597
38894
|
const when = Date.parse(said);
|
|
38598
38895
|
return said === "" || Number.isNaN(when) ? null : when;
|
|
38599
38896
|
} catch {
|
|
38600
38897
|
return null;
|
|
38601
38898
|
}
|
|
38602
38899
|
}
|
|
38603
|
-
function windowsImage(pid) {
|
|
38900
|
+
async function windowsImage(pid) {
|
|
38604
38901
|
try {
|
|
38605
|
-
const csv =
|
|
38902
|
+
const { stdout: csv } = await execFileAsync("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
38606
38903
|
encoding: "utf8",
|
|
38607
38904
|
timeout: 15000,
|
|
38608
38905
|
windowsHide: true
|
|
@@ -38612,11 +38909,11 @@ function windowsImage(pid) {
|
|
|
38612
38909
|
return null;
|
|
38613
38910
|
}
|
|
38614
38911
|
}
|
|
38615
|
-
function stillTheRecordedRun(record) {
|
|
38616
|
-
return judgeRun(record, runningAs(record.pid), "confirmed");
|
|
38912
|
+
async function stillTheRecordedRun(record) {
|
|
38913
|
+
return judgeRun(record, await runningAs(record.pid), "confirmed");
|
|
38617
38914
|
}
|
|
38618
|
-
function couldStillBeTheRecordedRun(record) {
|
|
38619
|
-
return judgeRun(record, runningAs(record.pid), "possible");
|
|
38915
|
+
async function couldStillBeTheRecordedRun(record) {
|
|
38916
|
+
return judgeRun(record, await runningAs(record.pid), "possible");
|
|
38620
38917
|
}
|
|
38621
38918
|
function judgeRun(record, running, needed) {
|
|
38622
38919
|
if (running === null) {
|
|
@@ -38643,8 +38940,8 @@ function judgeRun(record, running, needed) {
|
|
|
38643
38940
|
const project = process.platform === "win32" ? record.projectPath.toLowerCase() : record.projectPath;
|
|
38644
38941
|
return said.includes(project);
|
|
38645
38942
|
}
|
|
38646
|
-
function stillTheAnnouncedGame(pid, projectPath, announcedAt) {
|
|
38647
|
-
return judgeAnnouncedGame(runningAs(pid), projectPath, announcedAt);
|
|
38943
|
+
async function stillTheAnnouncedGame(pid, projectPath, announcedAt) {
|
|
38944
|
+
return judgeAnnouncedGame(await runningAs(pid), projectPath, announcedAt);
|
|
38648
38945
|
}
|
|
38649
38946
|
function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
38650
38947
|
if (running?.kind !== "commandLine" || running.startedAt === undefined) {
|
|
@@ -38661,7 +38958,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
38661
38958
|
}
|
|
38662
38959
|
function clearRunRecord(projectPath) {
|
|
38663
38960
|
for (const directory of runtimeDirectories()) {
|
|
38664
|
-
const shared =
|
|
38961
|
+
const shared = join21(directory, "runs", SHARED_NOTE);
|
|
38665
38962
|
const sharedRecord = recordAt(shared);
|
|
38666
38963
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
38667
38964
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -38683,7 +38980,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38683
38980
|
const kept = new Set(everyRunRecord().map((record) => record.transcript));
|
|
38684
38981
|
let entries;
|
|
38685
38982
|
try {
|
|
38686
|
-
entries =
|
|
38983
|
+
entries = readdirSync8(directory);
|
|
38687
38984
|
} catch {
|
|
38688
38985
|
return;
|
|
38689
38986
|
}
|
|
@@ -38691,7 +38988,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38691
38988
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
38692
38989
|
continue;
|
|
38693
38990
|
}
|
|
38694
|
-
const path =
|
|
38991
|
+
const path = join21(directory, entry);
|
|
38695
38992
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
38696
38993
|
continue;
|
|
38697
38994
|
}
|
|
@@ -38700,37 +38997,37 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
38700
38997
|
} catch {}
|
|
38701
38998
|
}
|
|
38702
38999
|
}
|
|
38703
|
-
var SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
|
|
39000
|
+
var execFileAsync, SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
|
|
38704
39001
|
var init_run_record = __esm(() => {
|
|
38705
39002
|
init_paths();
|
|
38706
39003
|
init_process_children();
|
|
38707
39004
|
init_runtime_client();
|
|
39005
|
+
init_windows_ask();
|
|
39006
|
+
execFileAsync = promisify5(execFile5);
|
|
38708
39007
|
});
|
|
38709
39008
|
|
|
38710
39009
|
// src/server.ts
|
|
38711
|
-
import { execFile as
|
|
39010
|
+
import { execFile as execFile6, spawn as spawn3 } from "node:child_process";
|
|
38712
39011
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
38713
39012
|
import {
|
|
38714
39013
|
appendFileSync,
|
|
38715
39014
|
closeSync as closeSync2,
|
|
38716
39015
|
constants,
|
|
38717
39016
|
copyFileSync,
|
|
38718
|
-
existsSync as
|
|
39017
|
+
existsSync as existsSync15,
|
|
38719
39018
|
fstatSync,
|
|
38720
39019
|
mkdirSync as mkdirSync12,
|
|
38721
|
-
mkdtempSync as mkdtempSync2,
|
|
38722
39020
|
openSync as openSync3,
|
|
38723
|
-
readdirSync as
|
|
39021
|
+
readdirSync as readdirSync9,
|
|
38724
39022
|
readFileSync as readFileSync19,
|
|
38725
39023
|
readSync as readSync2,
|
|
38726
39024
|
rmdirSync as rmdirSync2,
|
|
38727
39025
|
statSync as statSync8
|
|
38728
39026
|
} 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";
|
|
39027
|
+
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
39028
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
38732
39029
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
38733
|
-
import { promisify as
|
|
39030
|
+
import { promisify as promisify6 } from "node:util";
|
|
38734
39031
|
function captureDestinationRefusal(outputPath, projects) {
|
|
38735
39032
|
if (/^(res|user):\/\//.test(outputPath)) {
|
|
38736
39033
|
return `outputPath ${outputPath} is a Godot path; a capture is saved outside the project, at an absolute path on this machine.`;
|
|
@@ -38742,15 +39039,15 @@ function captureDestinationRefusal(outputPath, projects) {
|
|
|
38742
39039
|
return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
|
|
38743
39040
|
}
|
|
38744
39041
|
const directory = realPathOr(dirname15(outputPath));
|
|
38745
|
-
if (!
|
|
39042
|
+
if (!existsSync15(directory)) {
|
|
38746
39043
|
return `outputPath's directory ${dirname15(outputPath)} does not exist; a capture creates no directories.`;
|
|
38747
39044
|
}
|
|
38748
|
-
const where =
|
|
39045
|
+
const where = join22(directory, basename5(outputPath));
|
|
38749
39046
|
const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
38750
39047
|
if (inside !== undefined) {
|
|
38751
39048
|
return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
|
|
38752
39049
|
}
|
|
38753
|
-
if (
|
|
39050
|
+
if (existsSync15(where)) {
|
|
38754
39051
|
return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
|
|
38755
39052
|
}
|
|
38756
39053
|
return null;
|
|
@@ -38906,7 +39203,8 @@ function aboutTheChildren(ended, throughEditor) {
|
|
|
38906
39203
|
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
39204
|
}
|
|
38908
39205
|
if (ended.unknown === "platform") {
|
|
38909
|
-
|
|
39206
|
+
const why = whyTheProcessTreeFailed();
|
|
39207
|
+
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
39208
|
}
|
|
38911
39209
|
if (ended.unknown === "no process") {
|
|
38912
39210
|
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 +39285,6 @@ function runtimeVerdict(endpoint, after) {
|
|
|
38987
39285
|
heldAt: null
|
|
38988
39286
|
};
|
|
38989
39287
|
}
|
|
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
39288
|
function isOfType(value, kind) {
|
|
39002
39289
|
switch (kind) {
|
|
39003
39290
|
case "string":
|
|
@@ -39189,13 +39476,13 @@ function aboveTheRunner(entry) {
|
|
|
39189
39476
|
}
|
|
39190
39477
|
function elsewhereIn(projectPath, asked) {
|
|
39191
39478
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
39192
|
-
if (wanted === "" ||
|
|
39479
|
+
if (wanted === "" || existsSync15(join22(projectPath, wanted))) {
|
|
39193
39480
|
return "";
|
|
39194
39481
|
}
|
|
39195
39482
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
39196
39483
|
let near = [];
|
|
39197
39484
|
try {
|
|
39198
|
-
near =
|
|
39485
|
+
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
39486
|
} catch {
|
|
39200
39487
|
return "";
|
|
39201
39488
|
}
|
|
@@ -39211,7 +39498,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
39211
39498
|
return false;
|
|
39212
39499
|
}
|
|
39213
39500
|
try {
|
|
39214
|
-
return readFileSync19(
|
|
39501
|
+
return readFileSync19(join22(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
39215
39502
|
} catch {
|
|
39216
39503
|
return false;
|
|
39217
39504
|
}
|
|
@@ -39236,7 +39523,7 @@ function needsItsOwnProcess(asked) {
|
|
|
39236
39523
|
class GodotServer {
|
|
39237
39524
|
mcp;
|
|
39238
39525
|
locator = new GodotLocator;
|
|
39239
|
-
operationsScript =
|
|
39526
|
+
operationsScript = join22(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
39240
39527
|
godotBridge;
|
|
39241
39528
|
tools = buildToolDefinitions();
|
|
39242
39529
|
activeProcess = null;
|
|
@@ -39248,6 +39535,7 @@ class GodotServer {
|
|
|
39248
39535
|
lastGodotVersion = null;
|
|
39249
39536
|
lspClient = null;
|
|
39250
39537
|
dapClient = null;
|
|
39538
|
+
adapterPassed = null;
|
|
39251
39539
|
breakpointsSeededOn = null;
|
|
39252
39540
|
bridgeStartupError = null;
|
|
39253
39541
|
bridgeRetry = null;
|
|
@@ -39306,6 +39594,7 @@ class GodotServer {
|
|
|
39306
39594
|
this.handleShutdown("stdin:end", 0);
|
|
39307
39595
|
});
|
|
39308
39596
|
console.error("gdharness running on stdio");
|
|
39597
|
+
sweepAbandonedScratch();
|
|
39309
39598
|
try {
|
|
39310
39599
|
await this.takeOverFromAPredecessor();
|
|
39311
39600
|
await this.godotBridge.start();
|
|
@@ -39632,7 +39921,7 @@ class GodotServer {
|
|
|
39632
39921
|
this.callsSinceFeedback = 0;
|
|
39633
39922
|
return {
|
|
39634
39923
|
...answer,
|
|
39635
|
-
content: [...answer.content, { type: "text", text: feedbackNotice() }]
|
|
39924
|
+
content: [...answer.content, { type: "text", text: feedbackNotice(FEEDBACK_NOTICE_EVERY) }]
|
|
39636
39925
|
};
|
|
39637
39926
|
}
|
|
39638
39927
|
validateArguments(spec, args) {
|
|
@@ -39976,8 +40265,8 @@ class GodotServer {
|
|
|
39976
40265
|
if (path === undefined) {
|
|
39977
40266
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
39978
40267
|
}
|
|
39979
|
-
const file =
|
|
39980
|
-
if (!
|
|
40268
|
+
const file = join22(path, "project.godot");
|
|
40269
|
+
if (!existsSync15(file)) {
|
|
39981
40270
|
return {
|
|
39982
40271
|
ok: false,
|
|
39983
40272
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -40138,13 +40427,13 @@ class GodotServer {
|
|
|
40138
40427
|
let sawAScan = false;
|
|
40139
40428
|
let scanEndedAt = null;
|
|
40140
40429
|
for (;; ) {
|
|
40141
|
-
const asked = Date.now();
|
|
40142
40430
|
let status;
|
|
40143
40431
|
try {
|
|
40144
40432
|
status = asParams(await this.godotBridge.invokeTool("scan_status", {}));
|
|
40145
40433
|
} catch {
|
|
40146
40434
|
return { waitedMs: Date.now() - started, stillScanning: false };
|
|
40147
40435
|
}
|
|
40436
|
+
const answered = Date.now();
|
|
40148
40437
|
if (status["scanning"] === true || status["importing"] === true || status["pending"] === true) {
|
|
40149
40438
|
sawAScan = true;
|
|
40150
40439
|
if (Date.now() - started >= SCAN_WAIT_MS) {
|
|
@@ -40154,7 +40443,7 @@ class GodotServer {
|
|
|
40154
40443
|
continue;
|
|
40155
40444
|
}
|
|
40156
40445
|
const since = readNumber(status, "sinceScanFinishedMs");
|
|
40157
|
-
scanEndedAt = since !== undefined && since >= 0 ?
|
|
40446
|
+
scanEndedAt = since !== undefined && since >= 0 ? answered - since : sawAScan ? answered : null;
|
|
40158
40447
|
break;
|
|
40159
40448
|
}
|
|
40160
40449
|
let waitedForTheWrite = false;
|
|
@@ -40573,11 +40862,11 @@ class GodotServer {
|
|
|
40573
40862
|
return this.createErrorResponse(`Cannot create ${dirname15(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
40574
40863
|
}
|
|
40575
40864
|
const debug = readBoolean(args, "debug") ?? false;
|
|
40576
|
-
const exportLogs =
|
|
40865
|
+
const exportLogs = scratchDirectory("export");
|
|
40577
40866
|
const exportArgs = [
|
|
40578
40867
|
"--headless",
|
|
40579
40868
|
"--log-file",
|
|
40580
|
-
|
|
40869
|
+
join22(exportLogs, "engine.log"),
|
|
40581
40870
|
"--path",
|
|
40582
40871
|
project.value.path,
|
|
40583
40872
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -40609,7 +40898,7 @@ class GodotServer {
|
|
|
40609
40898
|
log.finish();
|
|
40610
40899
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
40611
40900
|
const verdict = {
|
|
40612
|
-
exported: ending.exitCode === 0 && log.count("error") === 0 &&
|
|
40901
|
+
exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
40613
40902
|
preset,
|
|
40614
40903
|
outputPath: output.relativePath,
|
|
40615
40904
|
debug,
|
|
@@ -40645,7 +40934,7 @@ class GodotServer {
|
|
|
40645
40934
|
return contained.response;
|
|
40646
40935
|
}
|
|
40647
40936
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
40648
|
-
if (!
|
|
40937
|
+
if (!existsSync15(join22(project.value.path, runner))) {
|
|
40649
40938
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
40650
40939
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
40651
40940
|
]);
|
|
@@ -40681,7 +40970,7 @@ class GodotServer {
|
|
|
40681
40970
|
];
|
|
40682
40971
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
40683
40972
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
40684
|
-
const userData =
|
|
40973
|
+
const userData = scratchDirectory("tests");
|
|
40685
40974
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
40686
40975
|
const console2 = [];
|
|
40687
40976
|
let lastSaid = Date.now();
|
|
@@ -40708,14 +40997,17 @@ class GodotServer {
|
|
|
40708
40997
|
resolve(false);
|
|
40709
40998
|
});
|
|
40710
40999
|
});
|
|
40711
|
-
|
|
41000
|
+
if (timedOut && run.process.pid !== undefined) {
|
|
41001
|
+
await untilGone([run.process.pid]);
|
|
41002
|
+
}
|
|
41003
|
+
const reportsDir = join22(project.value.path, ".godot", ...ours.split("/"));
|
|
40712
41004
|
let report = null;
|
|
40713
41005
|
let reportProblem = null;
|
|
40714
41006
|
try {
|
|
40715
|
-
const written =
|
|
41007
|
+
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
41008
|
const newest = written.at(-1);
|
|
40717
41009
|
if (newest !== undefined) {
|
|
40718
|
-
report = parseJUnit(readFileSync19(
|
|
41010
|
+
report = parseJUnit(readFileSync19(join22(reportsDir, newest, "results.xml"), "utf8"));
|
|
40719
41011
|
}
|
|
40720
41012
|
} catch (error) {
|
|
40721
41013
|
reportProblem = errorMessage(error);
|
|
@@ -40873,7 +41165,7 @@ class GodotServer {
|
|
|
40873
41165
|
const cached = cachedClasses(projectPath);
|
|
40874
41166
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
40875
41167
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40876
|
-
if (!contained.ok || !
|
|
41168
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
40877
41169
|
return null;
|
|
40878
41170
|
}
|
|
40879
41171
|
try {
|
|
@@ -40927,7 +41219,7 @@ class GodotServer {
|
|
|
40927
41219
|
}
|
|
40928
41220
|
sourceOfClass(projectPath, resourcePath) {
|
|
40929
41221
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40930
|
-
if (!contained.ok || !
|
|
41222
|
+
if (!contained.ok || !existsSync15(contained.absolutePath)) {
|
|
40931
41223
|
return null;
|
|
40932
41224
|
}
|
|
40933
41225
|
try {
|
|
@@ -40948,28 +41240,32 @@ class GodotServer {
|
|
|
40948
41240
|
async handleDAP(toolName, args) {
|
|
40949
41241
|
return handleDAPTool(this.dap(), toolName, args);
|
|
40950
41242
|
}
|
|
40951
|
-
adapterBelongsElsewhere() {
|
|
41243
|
+
async adapterBelongsElsewhere() {
|
|
40952
41244
|
const status = this.godotBridge.getStatus();
|
|
40953
41245
|
const ours = status.editorPid;
|
|
40954
41246
|
if (ours === undefined) {
|
|
40955
41247
|
return null;
|
|
40956
41248
|
}
|
|
41249
|
+
const connection = this.dapClient?.connection() ?? null;
|
|
41250
|
+
if (connection !== null && this.adapterPassed?.connection === connection && this.adapterPassed.editorPid === ours) {
|
|
41251
|
+
return null;
|
|
41252
|
+
}
|
|
40957
41253
|
const port = this.editorServes("dapPort", "GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT);
|
|
40958
|
-
const
|
|
41254
|
+
const askedAt = Date.now();
|
|
41255
|
+
const holder = await listeningPid(port);
|
|
41256
|
+
this.logDebug(`Asked who holds debug adapter port ${port} in ${Date.now() - askedAt}ms: ${holder ?? "not said"}`);
|
|
40959
41257
|
if (holder === null || holder === ours) {
|
|
41258
|
+
this.adapterPassed = connection === null ? null : { connection, editorPid: ours };
|
|
40960
41259
|
return null;
|
|
40961
41260
|
}
|
|
40962
41261
|
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
41262
|
}
|
|
40964
|
-
|
|
40965
|
-
return anEditorIsStillComing(listeningSince, this.launchedEditorIsUp());
|
|
40966
|
-
}
|
|
40967
|
-
launchedEditorIsUp() {
|
|
41263
|
+
async launchedEditorIsUp() {
|
|
40968
41264
|
const launched = this.launchedEditor;
|
|
40969
41265
|
if (launched === null || !alive(launched.pid)) {
|
|
40970
41266
|
return false;
|
|
40971
41267
|
}
|
|
40972
|
-
const now = startTimesOf([launched.pid]).get(launched.pid);
|
|
41268
|
+
const now = (await startTimesOf([launched.pid])).get(launched.pid);
|
|
40973
41269
|
return launched.startedAt === null || now === undefined || now === launched.startedAt;
|
|
40974
41270
|
}
|
|
40975
41271
|
dap() {
|
|
@@ -41058,8 +41354,9 @@ class GodotServer {
|
|
|
41058
41354
|
}
|
|
41059
41355
|
return owed;
|
|
41060
41356
|
}
|
|
41061
|
-
getEditorStatusPayload() {
|
|
41357
|
+
async getEditorStatusPayload() {
|
|
41062
41358
|
const status = this.godotBridge.getStatus();
|
|
41359
|
+
const launchedIsUp = !status.connected && await this.launchedEditorIsUp();
|
|
41063
41360
|
const isPortConflict = this.bridgeStartupError?.includes("EADDRINUSE") ?? false;
|
|
41064
41361
|
const stale = status.connected && editorIsStale(status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest());
|
|
41065
41362
|
const unfinished = this.restartLeftUnfinished(status.connected);
|
|
@@ -41068,14 +41365,14 @@ class GodotServer {
|
|
|
41068
41365
|
serverVersion: SERVER_VERSION,
|
|
41069
41366
|
addonIsStale: status.connected ? stale : undefined,
|
|
41070
41367
|
bridgeAvailable: this.bridgeStartupError === null,
|
|
41071
|
-
mayYetConnect: status.connected ? undefined : unfinished !== null ? false :
|
|
41368
|
+
mayYetConnect: status.connected ? undefined : unfinished !== null ? false : anEditorIsStillComing(status.listeningSince, launchedIsUp),
|
|
41072
41369
|
restartInterrupted: unfinished === null ? undefined : {
|
|
41073
41370
|
quitEditorPid: unfinished.editorPid,
|
|
41074
41371
|
quitAt: unfinished.quitAt,
|
|
41075
41372
|
byServerPid: unfinished.byPid,
|
|
41076
41373
|
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
41374
|
},
|
|
41078
|
-
awaitingLaunchedEditor: status.connected || !
|
|
41375
|
+
awaitingLaunchedEditor: status.connected || !launchedIsUp ? undefined : this.launchedEditor?.pid ?? undefined,
|
|
41079
41376
|
...status.connected ? this.whatTheLaunchedEditorDropped() : {},
|
|
41080
41377
|
startupError: this.bridgeStartupError,
|
|
41081
41378
|
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 +41461,7 @@ class GodotServer {
|
|
|
41164
41461
|
const asked = (async () => {
|
|
41165
41462
|
const playing = await this.editorPlayingState();
|
|
41166
41463
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
41167
|
-
const current = this.currentRun();
|
|
41464
|
+
const current = await this.currentRun();
|
|
41168
41465
|
const active = current === null ? false : await this.runStillGoing(current, playing?.playing ?? null);
|
|
41169
41466
|
return {
|
|
41170
41467
|
playing,
|
|
@@ -41172,14 +41469,15 @@ class GodotServer {
|
|
|
41172
41469
|
hold: current === null ? {} : await this.holdOf(current, active)
|
|
41173
41470
|
};
|
|
41174
41471
|
})();
|
|
41175
|
-
const [games, { playing, active, hold }, scan] = await Promise.all([
|
|
41472
|
+
const [games, { playing, active, hold }, scan, editorStatus] = await Promise.all([
|
|
41176
41473
|
pinged,
|
|
41177
41474
|
asked,
|
|
41178
|
-
this.editorScanState()
|
|
41475
|
+
this.editorScanState(),
|
|
41476
|
+
this.getEditorStatusPayload()
|
|
41179
41477
|
]);
|
|
41180
41478
|
return this.jsonTextResponse({
|
|
41181
41479
|
editor: {
|
|
41182
|
-
...
|
|
41480
|
+
...editorStatus,
|
|
41183
41481
|
debugPort: playing?.debugPort ?? this.godotBridge.getStatus().debugPort,
|
|
41184
41482
|
scan
|
|
41185
41483
|
},
|
|
@@ -41209,7 +41507,7 @@ class GodotServer {
|
|
|
41209
41507
|
return null;
|
|
41210
41508
|
}
|
|
41211
41509
|
try {
|
|
41212
|
-
return settingKeys(readFileSync19(
|
|
41510
|
+
return settingKeys(readFileSync19(join22(projectPath, "project.godot"), "utf8"));
|
|
41213
41511
|
} catch {
|
|
41214
41512
|
return null;
|
|
41215
41513
|
}
|
|
@@ -41358,7 +41656,7 @@ class GodotServer {
|
|
|
41358
41656
|
writeEditorLogNote(projectPath, launched.pid);
|
|
41359
41657
|
this.launchedEditor = {
|
|
41360
41658
|
pid: launched.pid,
|
|
41361
|
-
startedAt: startTimesOf([launched.pid]).get(launched.pid) ?? null
|
|
41659
|
+
startedAt: (await startTimesOf([launched.pid])).get(launched.pid) ?? null
|
|
41362
41660
|
};
|
|
41363
41661
|
return { pid: launched.pid, error: null };
|
|
41364
41662
|
}
|
|
@@ -41406,13 +41704,15 @@ class GodotServer {
|
|
|
41406
41704
|
}
|
|
41407
41705
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
41408
41706
|
let ended = null;
|
|
41409
|
-
const before = this.currentRun();
|
|
41707
|
+
const before = await this.currentRun();
|
|
41410
41708
|
if (before !== null && await this.runStillGoing(before)) {
|
|
41411
41709
|
this.logDebug("Ending the running game before starting another");
|
|
41710
|
+
const endingFrom = Date.now();
|
|
41412
41711
|
ended = {
|
|
41413
41712
|
pid: before.pid ?? this.announcedPidOf(before) ?? null,
|
|
41414
41713
|
ending: await this.endActiveGame("editor_run start, which ends the run that was going before it starts another", true)
|
|
41415
41714
|
};
|
|
41715
|
+
this.logDebug(`The running game was ${ended.ending} after ${Date.now() - endingFrom}ms`);
|
|
41416
41716
|
}
|
|
41417
41717
|
const alreadyPlaying = new Set(discoverRuntimes().map((endpoint) => endpoint.pid));
|
|
41418
41718
|
const wereHereFirst = this.everyRuntimeOfOurs();
|
|
@@ -41462,7 +41762,7 @@ class GodotServer {
|
|
|
41462
41762
|
}
|
|
41463
41763
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
41464
41764
|
const budgetMs = budget.ms;
|
|
41465
|
-
if (!
|
|
41765
|
+
if (!existsSync15(join22(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
41466
41766
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
41467
41767
|
}
|
|
41468
41768
|
let asking = null;
|
|
@@ -41473,12 +41773,12 @@ class GodotServer {
|
|
|
41473
41773
|
const lastSaid = () => editorSaysGoing ? "playing" : "not playing";
|
|
41474
41774
|
const endpoint = await announcedSince(projectPath, before, {
|
|
41475
41775
|
budgetMs,
|
|
41476
|
-
accept: (one) => this.isTheGameOf(this.
|
|
41776
|
+
accept: (one) => this.isTheGameOf(this.activeProcess, one, projectPath),
|
|
41477
41777
|
giveUp: () => {
|
|
41478
41778
|
if (this.dapClient?.isStopped() === true) {
|
|
41479
41779
|
return true;
|
|
41480
41780
|
}
|
|
41481
|
-
const run = this.
|
|
41781
|
+
const run = this.activeProcess;
|
|
41482
41782
|
if (run === null || !run.throughEditor) {
|
|
41483
41783
|
return !stillRunning(run);
|
|
41484
41784
|
}
|
|
@@ -41496,7 +41796,7 @@ class GodotServer {
|
|
|
41496
41796
|
});
|
|
41497
41797
|
const asked = editorAsks === 0 ? "editor not asked" : `editor asked ${editorAsks === 1 ? "once" : `${editorAsks} times`}, last said ${lastSaid()}`;
|
|
41498
41798
|
this.logDebug(`announce wait ended after ${Date.now() - waitBegan}ms: ${endpoint === null ? "nothing announced" : `pid ${endpoint.pid} announced`}, ${asked}`);
|
|
41499
|
-
const going = this.currentRun();
|
|
41799
|
+
const going = await this.currentRun();
|
|
41500
41800
|
if (endpoint !== null && going !== null && going.announcedPid === undefined) {
|
|
41501
41801
|
this.tie(going, endpoint);
|
|
41502
41802
|
}
|
|
@@ -41537,8 +41837,10 @@ class GodotServer {
|
|
|
41537
41837
|
}
|
|
41538
41838
|
async playThroughEditor(scene, refreshedClasses, scanned, projectPath, alreadyPlaying, runtimeWaitMs, ended) {
|
|
41539
41839
|
const log = new GameLog;
|
|
41840
|
+
const connectingFrom = Date.now();
|
|
41540
41841
|
try {
|
|
41541
41842
|
await this.dap().connect();
|
|
41843
|
+
this.logDebug(`The debug adapter was connected after ${Date.now() - connectingFrom}ms`);
|
|
41542
41844
|
this.dapClient?.getOutput(true);
|
|
41543
41845
|
} catch (error) {
|
|
41544
41846
|
return this.createErrorResponse(`The editor is connected but its debug adapter is not: ${errorMessage(error)}`, [
|
|
@@ -41546,7 +41848,7 @@ class GodotServer {
|
|
|
41546
41848
|
"GDHARNESS_DAP_PORT points this server at another one"
|
|
41547
41849
|
]);
|
|
41548
41850
|
}
|
|
41549
|
-
const stranger = this.adapterBelongsElsewhere();
|
|
41851
|
+
const stranger = await this.adapterBelongsElsewhere();
|
|
41550
41852
|
if (stranger !== null) {
|
|
41551
41853
|
return this.createErrorResponse(stranger, [
|
|
41552
41854
|
"editor_status names the editor on the bridge and the port it says it serves",
|
|
@@ -41554,7 +41856,9 @@ class GodotServer {
|
|
|
41554
41856
|
"Closing the other editor, or moving its debug adapter port in its editor settings, frees this one"
|
|
41555
41857
|
]);
|
|
41556
41858
|
}
|
|
41859
|
+
const sendingFrom = Date.now();
|
|
41557
41860
|
const breakpoints = await this.dapHoldingBreakpointsOf(projectPath).reapplyBreakpoints();
|
|
41861
|
+
this.logDebug(`Breakpoints were sent again after ${Date.now() - sendingFrom}ms`);
|
|
41558
41862
|
const answer = await this.handleViaBridge("play_scene", scene === null ? {} : { scenePath: `res://${scene}` });
|
|
41559
41863
|
if (answer.isError === true) {
|
|
41560
41864
|
return answer;
|
|
@@ -41740,13 +42044,13 @@ class GodotServer {
|
|
|
41740
42044
|
arguments: [],
|
|
41741
42045
|
...running.command === undefined ? {} : { command: running.command }
|
|
41742
42046
|
};
|
|
41743
|
-
if (!stillTheRecordedRun(startedAs)) {
|
|
42047
|
+
if (!await stillTheRecordedRun(startedAs)) {
|
|
41744
42048
|
clearTheRecord();
|
|
41745
42049
|
running.endedHere = null;
|
|
41746
42050
|
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
42051
|
return "refused";
|
|
41748
42052
|
}
|
|
41749
|
-
const signalled = runningAs(running.pid);
|
|
42053
|
+
const signalled = await runningAs(running.pid);
|
|
41750
42054
|
const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
41751
42055
|
let landed = true;
|
|
41752
42056
|
try {
|
|
@@ -41767,7 +42071,7 @@ class GodotServer {
|
|
|
41767
42071
|
return gone ? "gone" : "lingering";
|
|
41768
42072
|
}
|
|
41769
42073
|
spawnGame(godotPath, cmdArgs, env) {
|
|
41770
|
-
const child =
|
|
42074
|
+
const child = spawn3(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
41771
42075
|
const cancelled = callSignal();
|
|
41772
42076
|
if (cancelled !== undefined) {
|
|
41773
42077
|
const end = () => {
|
|
@@ -41934,8 +42238,8 @@ class GodotServer {
|
|
|
41934
42238
|
}
|
|
41935
42239
|
return run.announcedPid;
|
|
41936
42240
|
}
|
|
41937
|
-
ownGameAmong(running, projectPath) {
|
|
41938
|
-
const run = this.currentRun();
|
|
42241
|
+
async ownGameAmong(running, projectPath) {
|
|
42242
|
+
const run = await this.currentRun();
|
|
41939
42243
|
if (run === null || !stillRunning(run)) {
|
|
41940
42244
|
return;
|
|
41941
42245
|
}
|
|
@@ -41946,7 +42250,7 @@ class GodotServer {
|
|
|
41946
42250
|
return its !== undefined && running.some((one) => one.pid === its) ? its : undefined;
|
|
41947
42251
|
}
|
|
41948
42252
|
async ownGameThroughTheTree(running, projectPath) {
|
|
41949
|
-
const run = this.currentRun();
|
|
42253
|
+
const run = await this.currentRun();
|
|
41950
42254
|
if (run === null || !stillRunning(run) || run.announcedPid !== undefined) {
|
|
41951
42255
|
return;
|
|
41952
42256
|
}
|
|
@@ -42032,7 +42336,7 @@ class GodotServer {
|
|
|
42032
42336
|
const editor = this.godotBridge.getStatus().editorPid;
|
|
42033
42337
|
return endpoint.editorPid === undefined || endpoint.editorPid !== editor;
|
|
42034
42338
|
}
|
|
42035
|
-
adoptRecordedRun() {
|
|
42339
|
+
async adoptRecordedRun() {
|
|
42036
42340
|
const project = this.ourProject();
|
|
42037
42341
|
const record = project === null ? null : readRunRecord(project);
|
|
42038
42342
|
if (record === null || !this.couldBeOurs(record.projectPath)) {
|
|
@@ -42053,7 +42357,7 @@ class GodotServer {
|
|
|
42053
42357
|
pickedUp: true
|
|
42054
42358
|
};
|
|
42055
42359
|
this.drainTranscript(adopted);
|
|
42056
|
-
if (!alive(record.pid) || !couldStillBeTheRecordedRun(record)) {
|
|
42360
|
+
if (!alive(record.pid) || !await couldStillBeTheRecordedRun(record)) {
|
|
42057
42361
|
adopted.log.finish();
|
|
42058
42362
|
adopted.exitCode = record.exitCode ?? null;
|
|
42059
42363
|
adopted.exitSignal = record.exitSignal ?? null;
|
|
@@ -42134,8 +42438,8 @@ class GodotServer {
|
|
|
42134
42438
|
const status = this.godotBridge.getStatus();
|
|
42135
42439
|
return this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
|
|
42136
42440
|
}
|
|
42137
|
-
currentRun() {
|
|
42138
|
-
return this.activeProcess ?? this.adoptRecordedRun();
|
|
42441
|
+
async currentRun() {
|
|
42442
|
+
return this.activeProcess ?? await this.adoptRecordedRun();
|
|
42139
42443
|
}
|
|
42140
42444
|
async checkBoot(godotPath, projectPath, scene, args, userArgs, scanned, asked = {}) {
|
|
42141
42445
|
const frames = readPositiveNumber(args, "frames") ?? 3;
|
|
@@ -42174,10 +42478,14 @@ class GodotServer {
|
|
|
42174
42478
|
}
|
|
42175
42479
|
async handleWaitForRun(args) {
|
|
42176
42480
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
42177
|
-
const run = this.currentRun();
|
|
42481
|
+
const run = await this.currentRun();
|
|
42178
42482
|
if (!run) {
|
|
42179
42483
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
42180
42484
|
}
|
|
42485
|
+
const elsewhere = this.anotherProjectsRun(run.projectPath, args, "waited on");
|
|
42486
|
+
if (elsewhere !== null) {
|
|
42487
|
+
return elsewhere;
|
|
42488
|
+
}
|
|
42181
42489
|
const budgetMs = readPositiveNumber(args, "timeoutMs") ?? WAIT_FOR_RUN_MS;
|
|
42182
42490
|
const until = Date.now() + budgetMs;
|
|
42183
42491
|
const cancelled = callSignal();
|
|
@@ -42224,7 +42532,7 @@ class GodotServer {
|
|
|
42224
42532
|
await delay3(50);
|
|
42225
42533
|
}
|
|
42226
42534
|
}
|
|
42227
|
-
handleEditorConsole(args) {
|
|
42535
|
+
async handleEditorConsole(args) {
|
|
42228
42536
|
const status = this.godotBridge.getStatus();
|
|
42229
42537
|
const projectPath = status.projectPath ?? this.ownProject ?? undefined;
|
|
42230
42538
|
if (projectPath === undefined) {
|
|
@@ -42261,10 +42569,11 @@ class GodotServer {
|
|
|
42261
42569
|
const filtered = severity !== "info" || contains !== undefined;
|
|
42262
42570
|
const grouped = bursts(everything, readNonNegativeNumber(args, "before") ?? 3, filtered ? (entry) => answersTo(entry, severity, contains) : undefined);
|
|
42263
42571
|
const shown = grouped.slice(0, MOST_GROUPS);
|
|
42572
|
+
const stillComing = !status.connected && await this.launchedEditorIsUp();
|
|
42264
42573
|
return this.jsonTextResponse({
|
|
42265
42574
|
editorPid: editorPid ?? undefined,
|
|
42266
42575
|
projectPath,
|
|
42267
|
-
...status.connected ? {} :
|
|
42576
|
+
...status.connected ? {} : stillComing ? { stillConnecting: true } : { exitedBeforeConnecting: true },
|
|
42268
42577
|
capturedIn: console2.path,
|
|
42269
42578
|
...console2.of === undefined ? {} : {
|
|
42270
42579
|
readFromTheStart: console2.readBytes,
|
|
@@ -42288,7 +42597,7 @@ class GodotServer {
|
|
|
42288
42597
|
return this.handleEditorConsole(args);
|
|
42289
42598
|
}
|
|
42290
42599
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
42291
|
-
const run = this.currentRun();
|
|
42600
|
+
const run = await this.currentRun();
|
|
42292
42601
|
if (!run) {
|
|
42293
42602
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
42294
42603
|
}
|
|
@@ -42327,7 +42636,7 @@ class GodotServer {
|
|
|
42327
42636
|
notes.push(endedWithoutACode({
|
|
42328
42637
|
throughEditor: run.throughEditor,
|
|
42329
42638
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
42330
|
-
couldAnnounce: run.projectPath !== null &&
|
|
42639
|
+
couldAnnounce: run.projectPath !== null && existsSync15(join22(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
42331
42640
|
endedHere: typeof run.endedHere === "string",
|
|
42332
42641
|
pickedUp: run.pickedUp === true
|
|
42333
42642
|
}));
|
|
@@ -42365,16 +42674,27 @@ class GodotServer {
|
|
|
42365
42674
|
entries: forAnswer(selected.entries)
|
|
42366
42675
|
});
|
|
42367
42676
|
}
|
|
42677
|
+
anotherProjectsRun(runsProject, args, act) {
|
|
42678
|
+
const named = readNonEmptyString(args, "projectPath");
|
|
42679
|
+
if (named === undefined || runsProject === null || isSameDirectory(runsProject, named)) {
|
|
42680
|
+
return null;
|
|
42681
|
+
}
|
|
42682
|
+
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.`);
|
|
42683
|
+
}
|
|
42368
42684
|
async handleStopProject(args) {
|
|
42369
42685
|
await this.pickUpWhatTheEditorIsPlaying();
|
|
42370
|
-
const stopped = this.currentRun();
|
|
42686
|
+
const stopped = await this.currentRun();
|
|
42371
42687
|
const named = readNumber(args, "pid");
|
|
42372
42688
|
if (named !== undefined && (stopped === null || named !== stopped.pid && named !== this.announcedPidOf(stopped))) {
|
|
42373
|
-
return await this.stopAnnouncedGame(named, readBoolean(args, "andChildren") === true);
|
|
42689
|
+
return await this.stopAnnouncedGame(named, readBoolean(args, "andChildren") === true, args);
|
|
42374
42690
|
}
|
|
42375
42691
|
if (!stopped) {
|
|
42376
42692
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
42377
42693
|
}
|
|
42694
|
+
const elsewhere = this.anotherProjectsRun(stopped.projectPath, args, "stopped");
|
|
42695
|
+
if (elsewhere !== null) {
|
|
42696
|
+
return elsewhere;
|
|
42697
|
+
}
|
|
42378
42698
|
this.drainEditorOutput(stopped);
|
|
42379
42699
|
this.drainTranscript(stopped);
|
|
42380
42700
|
const wasRunning = await this.runStillGoing(stopped);
|
|
@@ -42416,16 +42736,22 @@ class GodotServer {
|
|
|
42416
42736
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
42417
42737
|
});
|
|
42418
42738
|
}
|
|
42419
|
-
async stopAnnouncedGame(pid, andChildren) {
|
|
42739
|
+
async stopAnnouncedGame(pid, andChildren, args) {
|
|
42420
42740
|
const announced = runtimesAnnounced();
|
|
42421
42741
|
const game = [
|
|
42422
42742
|
...this.allAnnouncedForOurProject(announced.running),
|
|
42423
42743
|
...this.allAnnouncedForOurProject(announced.unspoken)
|
|
42424
42744
|
].find((one) => one.pid === pid);
|
|
42745
|
+
if (game !== undefined) {
|
|
42746
|
+
const elsewhere = this.anotherProjectsRun(game.project.path, args, "signalled");
|
|
42747
|
+
if (elsewhere !== null) {
|
|
42748
|
+
return elsewhere;
|
|
42749
|
+
}
|
|
42750
|
+
}
|
|
42425
42751
|
if (game === undefined) {
|
|
42426
42752
|
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
42753
|
}
|
|
42428
|
-
const read = game.announcedAt === undefined ? null : stillTheAnnouncedGame(pid, game.project.path, game.announcedAt);
|
|
42754
|
+
const read = game.announcedAt === undefined ? null : await stillTheAnnouncedGame(pid, game.project.path, game.announcedAt);
|
|
42429
42755
|
if (read === null) {
|
|
42430
42756
|
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
42757
|
}
|
|
@@ -42623,6 +42949,8 @@ class GodotServer {
|
|
|
42623
42949
|
}
|
|
42624
42950
|
const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
|
|
42625
42951
|
const unseen = checked.unseen;
|
|
42952
|
+
const broughtIn = blind.unseen.map((one) => one.className).filter((name) => !unseen.some((still) => still.className === name));
|
|
42953
|
+
const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
|
|
42626
42954
|
const stillGone = lost.filter((name) => !restored.includes(name));
|
|
42627
42955
|
const notes = [];
|
|
42628
42956
|
if (busy) {
|
|
@@ -42634,13 +42962,16 @@ class GodotServer {
|
|
|
42634
42962
|
} else if (unseen.length > 0) {
|
|
42635
42963
|
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
42964
|
}
|
|
42965
|
+
if (dependents !== null && dependents.notReloaded.length > 0) {
|
|
42966
|
+
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.`);
|
|
42967
|
+
}
|
|
42637
42968
|
if (stillAtMissingPaths.length > 0) {
|
|
42638
42969
|
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
42970
|
} else if (dropped.length > 0) {
|
|
42640
42971
|
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
42972
|
}
|
|
42642
42973
|
return this.jsonTextResponse({
|
|
42643
|
-
ok: !busy && unseen.length === 0 && stillGone.length === 0 && stillAtMissingPaths.length === 0 && checked.unchecked === undefined,
|
|
42974
|
+
ok: !busy && unseen.length === 0 && stillGone.length === 0 && stillAtMissingPaths.length === 0 && (dependents === null || dependents.notReloaded.length === 0) && checked.unchecked === undefined,
|
|
42644
42975
|
stillWorking: busy,
|
|
42645
42976
|
waitedMs: Date.now() - started,
|
|
42646
42977
|
waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
|
|
@@ -42648,6 +42979,9 @@ class GodotServer {
|
|
|
42648
42979
|
reloadedMethods: reloaded.methods,
|
|
42649
42980
|
reloadProblem: reloaded.problem,
|
|
42650
42981
|
unseenByEditor: unseen.length > 0 ? unseen : undefined,
|
|
42982
|
+
broughtIn: broughtIn.length > 0 ? broughtIn : undefined,
|
|
42983
|
+
dependentsReloaded: dependents !== null && dependents.reloaded.length > 0 ? dependents.reloaded : undefined,
|
|
42984
|
+
dependentsNotReloaded: dependents !== null && dependents.notReloaded.length > 0 ? dependents.notReloaded : undefined,
|
|
42651
42985
|
cacheLost: lost.length > 0 ? lost : undefined,
|
|
42652
42986
|
cacheRestored: restored.length > 0 ? restored : undefined,
|
|
42653
42987
|
cacheDropped: dropped.length > 0 ? dropped : undefined,
|
|
@@ -42656,6 +42990,33 @@ class GodotServer {
|
|
|
42656
42990
|
note: notes.length > 0 ? notes.join(" ") : undefined
|
|
42657
42991
|
});
|
|
42658
42992
|
}
|
|
42993
|
+
async reloadDependents(projectPath, classes) {
|
|
42994
|
+
const reloaded = [];
|
|
42995
|
+
const notReloaded = [];
|
|
42996
|
+
for (const { script: scriptPath, tool } of scriptsNaming(projectPath, classes)) {
|
|
42997
|
+
if (tool) {
|
|
42998
|
+
notReloaded.push({
|
|
42999
|
+
scriptPath,
|
|
43000
|
+
problem: "a @tool script, which runs inside the editor, so it is not reloaded from here; editor_launch restart recompiles it"
|
|
43001
|
+
});
|
|
43002
|
+
continue;
|
|
43003
|
+
}
|
|
43004
|
+
try {
|
|
43005
|
+
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { scriptPath }));
|
|
43006
|
+
if (readArray(answer, "methods") === undefined) {
|
|
43007
|
+
notReloaded.push({
|
|
43008
|
+
scriptPath,
|
|
43009
|
+
problem: "the editor would not say what it has after reloading it"
|
|
43010
|
+
});
|
|
43011
|
+
} else {
|
|
43012
|
+
reloaded.push(scriptPath);
|
|
43013
|
+
}
|
|
43014
|
+
} catch (error) {
|
|
43015
|
+
notReloaded.push({ scriptPath, problem: errorMessage(error) });
|
|
43016
|
+
}
|
|
43017
|
+
}
|
|
43018
|
+
return { reloaded, notReloaded };
|
|
43019
|
+
}
|
|
42659
43020
|
async rebuildCacheAfterLoss(projectPath, lost) {
|
|
42660
43021
|
if (projectPath === "") {
|
|
42661
43022
|
return [];
|
|
@@ -42838,9 +43199,9 @@ class GodotServer {
|
|
|
42838
43199
|
return { heldAt: undefined, heldUnknown: true, heldNote: reply.message };
|
|
42839
43200
|
}
|
|
42840
43201
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
42841
|
-
const run = this.currentRun();
|
|
43202
|
+
const run = await this.currentRun();
|
|
42842
43203
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
42843
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
43204
|
+
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
43205
|
return null;
|
|
42845
43206
|
}
|
|
42846
43207
|
const already = runtimesAnnounced().running;
|
|
@@ -42866,8 +43227,8 @@ class GodotServer {
|
|
|
42866
43227
|
}
|
|
42867
43228
|
const endpoint = await announcedSince(project, new Set(already.map((one) => one.pid)), {
|
|
42868
43229
|
budgetMs: Math.min(budgetMs, remaining),
|
|
42869
|
-
accept: (one) => this.isTheGameOf(this.
|
|
42870
|
-
giveUp: () => this.
|
|
43230
|
+
accept: (one) => this.isTheGameOf(this.activeProcess, one, project),
|
|
43231
|
+
giveUp: () => this.activeProcess !== run || this.dapClient?.isStopped() === true || !stillRunning(run)
|
|
42871
43232
|
});
|
|
42872
43233
|
if (endpoint !== null) {
|
|
42873
43234
|
const tiedMeanwhile = () => run.announcedPid !== undefined;
|
|
@@ -42876,7 +43237,7 @@ class GodotServer {
|
|
|
42876
43237
|
}
|
|
42877
43238
|
return null;
|
|
42878
43239
|
}
|
|
42879
|
-
if (this.currentRun() !== run || !await this.runStillGoing(run)) {
|
|
43240
|
+
if (await this.currentRun() !== run || !await this.runStillGoing(run)) {
|
|
42880
43241
|
return null;
|
|
42881
43242
|
}
|
|
42882
43243
|
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 +43253,7 @@ class GodotServer {
|
|
|
42892
43253
|
}
|
|
42893
43254
|
const announced = runtimesAnnounced();
|
|
42894
43255
|
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);
|
|
43256
|
+
const own = typeof pid === "number" ? undefined : await this.ownGameAmong(announced.running, wanted) ?? await this.ownGameThroughTheTree(announced.running, wanted);
|
|
42896
43257
|
const choice = chooseRuntime(announced.running, wanted, announced.unspoken, typeof pid === "number" ? pid : own);
|
|
42897
43258
|
if ("problem" in choice) {
|
|
42898
43259
|
return this.createErrorResponse(choice.problem);
|
|
@@ -42919,8 +43280,8 @@ class GodotServer {
|
|
|
42919
43280
|
return this.createErrorResponse(refusal);
|
|
42920
43281
|
}
|
|
42921
43282
|
}
|
|
42922
|
-
const screenshotDir = expectsScreenshot ?
|
|
42923
|
-
const screenshotPath = screenshotDir ?
|
|
43283
|
+
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
43284
|
+
const screenshotPath = screenshotDir ? join22(screenshotDir, "capture.png") : null;
|
|
42924
43285
|
try {
|
|
42925
43286
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
42926
43287
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
@@ -43066,6 +43427,7 @@ var init_server3 = __esm(() => {
|
|
|
43066
43427
|
init_mcp();
|
|
43067
43428
|
init_stdio2();
|
|
43068
43429
|
init_types();
|
|
43430
|
+
init_alive();
|
|
43069
43431
|
init_boot_note();
|
|
43070
43432
|
init_breakpoint_note();
|
|
43071
43433
|
init_bridge_announce();
|
|
@@ -43101,7 +43463,7 @@ var init_server3 = __esm(() => {
|
|
|
43101
43463
|
init_setup();
|
|
43102
43464
|
init_tool_definitions();
|
|
43103
43465
|
init_update_check();
|
|
43104
|
-
run5 =
|
|
43466
|
+
run5 = promisify6(execFile6);
|
|
43105
43467
|
__dirname2 = dirname15(fileURLToPath5(import.meta.url));
|
|
43106
43468
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
43107
43469
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
@@ -43151,14 +43513,14 @@ var init_server3 = __esm(() => {
|
|
|
43151
43513
|
init_bridge_announce();
|
|
43152
43514
|
init_errors();
|
|
43153
43515
|
init_godot_path();
|
|
43154
|
-
import { existsSync as
|
|
43155
|
-
import { join as
|
|
43516
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
43517
|
+
import { join as join23, resolve as resolve6 } from "node:path";
|
|
43156
43518
|
|
|
43157
43519
|
// src/harnesses.ts
|
|
43158
43520
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
43159
43521
|
import { homedir as homedir2 } from "node:os";
|
|
43160
43522
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
43161
|
-
import
|
|
43523
|
+
import process4 from "node:process";
|
|
43162
43524
|
|
|
43163
43525
|
// node_modules/yaml/dist/index.js
|
|
43164
43526
|
var composer = require_composer();
|
|
@@ -43726,12 +44088,12 @@ var HARNESSES = [
|
|
|
43726
44088
|
}
|
|
43727
44089
|
];
|
|
43728
44090
|
function appDataRoot() {
|
|
43729
|
-
return
|
|
44091
|
+
return process4.env["APPDATA"] ?? join4(homedir2(), "AppData", "Roaming");
|
|
43730
44092
|
}
|
|
43731
44093
|
function homePath(path) {
|
|
43732
44094
|
return typeof path === "string" ? join4(homedir2(), path) : join4(appDataRoot(), path.appData);
|
|
43733
44095
|
}
|
|
43734
|
-
function fileFor(harness, platform =
|
|
44096
|
+
function fileFor(harness, platform = process4.platform) {
|
|
43735
44097
|
if (typeof harness.file === "string") {
|
|
43736
44098
|
return harness.file;
|
|
43737
44099
|
}
|
|
@@ -43744,7 +44106,7 @@ function configPath(harness, projectPath) {
|
|
|
43744
44106
|
}
|
|
43745
44107
|
return homePath(file);
|
|
43746
44108
|
}
|
|
43747
|
-
function displayPath(harness, platform =
|
|
44109
|
+
function displayPath(harness, platform = process4.platform) {
|
|
43748
44110
|
const file = fileFor(harness, platform);
|
|
43749
44111
|
if (typeof file !== "string") {
|
|
43750
44112
|
return `%APPDATA%/${file.appData.replaceAll("\\", "/")}`;
|
|
@@ -44022,10 +44384,10 @@ init_headless();
|
|
|
44022
44384
|
init_issues();
|
|
44023
44385
|
|
|
44024
44386
|
// src/prompt.ts
|
|
44025
|
-
import
|
|
44387
|
+
import process8 from "node:process";
|
|
44026
44388
|
import { createInterface } from "node:readline/promises";
|
|
44027
44389
|
function interactive() {
|
|
44028
|
-
return
|
|
44390
|
+
return process8.stdin.isTTY && process8.stdout.isTTY;
|
|
44029
44391
|
}
|
|
44030
44392
|
|
|
44031
44393
|
class Ask {
|
|
@@ -44034,7 +44396,7 @@ class Ask {
|
|
|
44034
44396
|
if (!interactive()) {
|
|
44035
44397
|
return fallback;
|
|
44036
44398
|
}
|
|
44037
|
-
this.line ??= createInterface({ input:
|
|
44399
|
+
this.line ??= createInterface({ input: process8.stdin, output: process8.stdout });
|
|
44038
44400
|
const answer = (await this.line.question(`${question} ${fallback ? "[Y/n]" : "[y/N]"} `)).trim();
|
|
44039
44401
|
return answer === "" ? fallback : answer.toLowerCase().startsWith("y");
|
|
44040
44402
|
}
|
|
@@ -44051,8 +44413,8 @@ init_setup();
|
|
|
44051
44413
|
// src/skill.ts
|
|
44052
44414
|
init_setup();
|
|
44053
44415
|
init_tool_definitions();
|
|
44054
|
-
import { existsSync as
|
|
44055
|
-
import { basename as basename2, dirname as dirname5, join as
|
|
44416
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
44417
|
+
import { basename as basename2, dirname as dirname5, join as join10 } from "node:path";
|
|
44056
44418
|
|
|
44057
44419
|
// src/tool-reference.ts
|
|
44058
44420
|
init_headless_operations();
|
|
@@ -44148,7 +44510,7 @@ function renderToolsMarkdown() {
|
|
|
44148
44510
|
|
|
44149
44511
|
// src/skill.ts
|
|
44150
44512
|
var SKILL_NAME = "gdharness";
|
|
44151
|
-
var SHARED_SKILLS =
|
|
44513
|
+
var SHARED_SKILLS = join10(".agents", "skills");
|
|
44152
44514
|
function skillMarkdown(version) {
|
|
44153
44515
|
return `---
|
|
44154
44516
|
name: ${SKILL_NAME}
|
|
@@ -44226,6 +44588,8 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
44226
44588
|
| 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
44589
|
| 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
44590
|
| Where a 3D node is on screen | \`runtime_inspect\` \`rect\`, rather than unprojecting by hand |
|
|
44591
|
+
| 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 |
|
|
44592
|
+
| 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
44593
|
| 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
44594
|
| 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
44595
|
| 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 +44701,27 @@ whatever the engine said on stderr comes back under \`engine_messages\`.
|
|
|
44337
44701
|
function skillFiles(version) {
|
|
44338
44702
|
return new Map([
|
|
44339
44703
|
["SKILL.md", skillMarkdown(version)],
|
|
44340
|
-
[
|
|
44704
|
+
[join10("references", "tools.md"), renderToolsMarkdown()]
|
|
44341
44705
|
]);
|
|
44342
44706
|
}
|
|
44343
44707
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
44344
|
-
const directories = [
|
|
44708
|
+
const directories = [join10(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
44345
44709
|
for (const harness of harnesses) {
|
|
44346
44710
|
if (harness.skills === undefined) {
|
|
44347
44711
|
continue;
|
|
44348
44712
|
}
|
|
44349
|
-
const own =
|
|
44350
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
44351
|
-
directories.push(
|
|
44713
|
+
const own = join10(projectPath, harness.skills.dir);
|
|
44714
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join10(own, SKILL_NAME))) {
|
|
44715
|
+
directories.push(join10(own, SKILL_NAME));
|
|
44352
44716
|
}
|
|
44353
44717
|
}
|
|
44354
44718
|
return directories;
|
|
44355
44719
|
}
|
|
44356
44720
|
function everySkillDirectory(projectPath, harnesses) {
|
|
44357
|
-
const directories = new Set([
|
|
44721
|
+
const directories = new Set([join10(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
44358
44722
|
for (const harness of harnesses) {
|
|
44359
44723
|
if (harness.skills !== undefined) {
|
|
44360
|
-
directories.add(
|
|
44724
|
+
directories.add(join10(projectPath, harness.skills.dir, SKILL_NAME));
|
|
44361
44725
|
}
|
|
44362
44726
|
}
|
|
44363
44727
|
return [...directories];
|
|
@@ -44365,7 +44729,7 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
44365
44729
|
function removeSkill(directories, projectPath) {
|
|
44366
44730
|
const removed = [];
|
|
44367
44731
|
for (const directory of directories) {
|
|
44368
|
-
if (
|
|
44732
|
+
if (existsSync9(directory)) {
|
|
44369
44733
|
rmSync5(directory, { recursive: true, force: true });
|
|
44370
44734
|
pruneEmpty(dirname5(directory), projectPath);
|
|
44371
44735
|
removed.push(directory);
|
|
@@ -44374,10 +44738,10 @@ function removeSkill(directories, projectPath) {
|
|
|
44374
44738
|
return removed;
|
|
44375
44739
|
}
|
|
44376
44740
|
function pruneEmpty(directory, projectPath) {
|
|
44377
|
-
const shared =
|
|
44741
|
+
const shared = join10(projectPath, ".agents");
|
|
44378
44742
|
let path = directory;
|
|
44379
44743
|
while (path !== projectPath && (basename2(path) === "skills" || path === shared)) {
|
|
44380
|
-
if (
|
|
44744
|
+
if (readdirSync5(path).length > 0) {
|
|
44381
44745
|
return;
|
|
44382
44746
|
}
|
|
44383
44747
|
rmSync5(path, { recursive: true, force: true });
|
|
@@ -44387,10 +44751,10 @@ function pruneEmpty(directory, projectPath) {
|
|
|
44387
44751
|
function writeSkill(directories, version) {
|
|
44388
44752
|
const files = skillFiles(version);
|
|
44389
44753
|
return directories.map((directory) => {
|
|
44390
|
-
const replaced =
|
|
44754
|
+
const replaced = existsSync9(directory);
|
|
44391
44755
|
rmSync5(directory, { recursive: true, force: true });
|
|
44392
44756
|
for (const [name, contents] of files) {
|
|
44393
|
-
const path =
|
|
44757
|
+
const path = join10(directory, name);
|
|
44394
44758
|
mkdirSync5(dirname5(path), { recursive: true });
|
|
44395
44759
|
writeFileSync6(path, contents, "utf8");
|
|
44396
44760
|
}
|
|
@@ -44400,13 +44764,13 @@ function writeSkill(directories, version) {
|
|
|
44400
44764
|
|
|
44401
44765
|
// src/version.ts
|
|
44402
44766
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
44403
|
-
import { dirname as dirname6, join as
|
|
44767
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
44404
44768
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
44405
44769
|
function getLocalVersion() {
|
|
44406
44770
|
const currentDirectory = dirname6(fileURLToPath2(import.meta.url));
|
|
44407
44771
|
const packageCandidates = [
|
|
44408
|
-
|
|
44409
|
-
|
|
44772
|
+
join11(currentDirectory, "..", "package.json"),
|
|
44773
|
+
join11(currentDirectory, "..", "..", "package.json")
|
|
44410
44774
|
];
|
|
44411
44775
|
for (const packagePath of packageCandidates) {
|
|
44412
44776
|
try {
|
|
@@ -44428,7 +44792,7 @@ class UsageError extends Refusal {
|
|
|
44428
44792
|
function projectArgument(at) {
|
|
44429
44793
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
44430
44794
|
const projectPath = resolve6(named ?? ".");
|
|
44431
|
-
if (!
|
|
44795
|
+
if (!existsSync16(join23(projectPath, "project.godot"))) {
|
|
44432
44796
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
44433
44797
|
}
|
|
44434
44798
|
return projectPath;
|
|
@@ -44467,7 +44831,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
44467
44831
|
return;
|
|
44468
44832
|
}
|
|
44469
44833
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
44470
|
-
if (!
|
|
44834
|
+
if (!existsSync16(join23(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
44471
44835
|
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
44836
|
}
|
|
44473
44837
|
}
|
|
@@ -44599,7 +44963,7 @@ async function setup() {
|
|
|
44599
44963
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
44600
44964
|
}
|
|
44601
44965
|
if (!args.includes("--no-skill")) {
|
|
44602
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
44966
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync16);
|
|
44603
44967
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
44604
44968
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
44605
44969
|
}
|
|
@@ -44693,7 +45057,7 @@ async function upgrade() {
|
|
|
44693
45057
|
moved.push(took.from);
|
|
44694
45058
|
}
|
|
44695
45059
|
}
|
|
44696
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
45060
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync16), version)) {
|
|
44697
45061
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
44698
45062
|
}
|
|
44699
45063
|
console.log(`
|
|
@@ -44702,7 +45066,7 @@ On ${version}. What is left for you:`);
|
|
|
44702
45066
|
` + ` so it gives an open editor nothing to restart for. editor_status says addonIsStale
|
|
44703
45067
|
` + " 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
45068
|
` + " editor_launch restart tool, which closes and reopens the window.");
|
|
44705
|
-
const serving = serverServing(projectPath);
|
|
45069
|
+
const serving = await serverServing(projectPath);
|
|
44706
45070
|
console.log(harnessNote({ moved, written, byHand }, [launch.command, ...launch.args].join(" "), serving === null ? null : { server: serving, isThisVersion: serving.version === version }));
|
|
44707
45071
|
}
|
|
44708
45072
|
function doctorReport(projectPath) {
|