gdharness 1.2.28 → 1.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +393 -167
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +259 -31
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -7
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +33 -7
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -3
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +89 -15
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +120 -0
- package/build/index.js +382 -167
- package/build/keeper.js +20 -17
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -3356,7 +3356,6 @@ function defectReport(where, error, context = {}) {
|
|
|
3356
3356
|
}
|
|
3357
3357
|
|
|
3358
3358
|
// src/server.ts
|
|
3359
|
-
import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
|
|
3360
3359
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3361
3360
|
import {
|
|
3362
3361
|
appendFileSync as appendFileSync2,
|
|
@@ -3371,12 +3370,11 @@ import {
|
|
|
3371
3370
|
readFileSync as readFileSync22,
|
|
3372
3371
|
readSync as readSync2,
|
|
3373
3372
|
rmdirSync as rmdirSync2,
|
|
3374
|
-
statSync as
|
|
3373
|
+
statSync as statSync10
|
|
3375
3374
|
} from "node:fs";
|
|
3376
3375
|
import { basename as basename6, dirname as dirname17, extname as extname3, isAbsolute as isAbsolute4, join as join24, normalize as normalize2, relative as relative4 } from "node:path";
|
|
3377
3376
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
3378
3377
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3379
|
-
import { promisify as promisify5 } from "node:util";
|
|
3380
3378
|
|
|
3381
3379
|
// node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
3382
3380
|
var __create2 = Object.create;
|
|
@@ -24886,7 +24884,7 @@ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync a
|
|
|
24886
24884
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
24887
24885
|
|
|
24888
24886
|
// src/runtime-client.ts
|
|
24889
|
-
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync, unlinkSync } from "node:fs";
|
|
24887
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync as statSync2, unlinkSync } from "node:fs";
|
|
24890
24888
|
import { createConnection } from "node:net";
|
|
24891
24889
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
24892
24890
|
import { dirname, join as join2, resolve as resolve2 } from "node:path";
|
|
@@ -24991,7 +24989,10 @@ function engineFlagsAmongGameArgs(args) {
|
|
|
24991
24989
|
return args.filter((one) => Object.hasOwn(ENGINE_FLAGS, one) || Object.hasOwn(SET_BY_ARGUMENT, one));
|
|
24992
24990
|
}
|
|
24993
24991
|
function runArguments(options) {
|
|
24994
|
-
const args =
|
|
24992
|
+
const args = [
|
|
24993
|
+
...options.headless ? ["--headless"] : [],
|
|
24994
|
+
...options.projectPath === null ? [] : ["--path", options.projectPath]
|
|
24995
|
+
];
|
|
24995
24996
|
if (options.silent === true && !options.headless) {
|
|
24996
24997
|
args.push("--audio-driver", "Dummy");
|
|
24997
24998
|
}
|
|
@@ -25049,7 +25050,7 @@ function environmentFor(options, variables = process.env) {
|
|
|
25049
25050
|
var SAVES_NOT_MOVED_NOTE = "On macOS the engine reads user:// off HOME and ignores the variables that move it elsewhere, measured on Godot 4.7.2, so this run wrote its saves where the player keeps theirs. A suite that saves a game has written into the same folder as the copy being played, and one that tidies up after itself may have removed real saves.";
|
|
25050
25051
|
|
|
25051
25052
|
// src/paths.ts
|
|
25052
|
-
import { realpathSync } from "node:fs";
|
|
25053
|
+
import { realpathSync, statSync } from "node:fs";
|
|
25053
25054
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
25054
25055
|
var RESOURCE_SCHEME = "res://";
|
|
25055
25056
|
var OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
|
|
@@ -25112,14 +25113,62 @@ function resolveWithinProject(projectPath, candidatePath) {
|
|
|
25112
25113
|
}
|
|
25113
25114
|
return { ok: true, absolutePath, relativePath };
|
|
25114
25115
|
}
|
|
25116
|
+
function exportedBuild(projectPath, candidatePath) {
|
|
25117
|
+
const found = resolveWithinProject(projectPath, candidatePath);
|
|
25118
|
+
if (!found.ok) {
|
|
25119
|
+
return found;
|
|
25120
|
+
}
|
|
25121
|
+
let real;
|
|
25122
|
+
try {
|
|
25123
|
+
real = realpathSync.native(found.absolutePath);
|
|
25124
|
+
} catch {
|
|
25125
|
+
return {
|
|
25126
|
+
ok: false,
|
|
25127
|
+
reason: `There is no ${found.relativePath} in the project to start: export the build first, with project_export run.`
|
|
25128
|
+
};
|
|
25129
|
+
}
|
|
25130
|
+
if (!isWithinRoot(realPathOr(projectPath), real)) {
|
|
25131
|
+
return {
|
|
25132
|
+
ok: false,
|
|
25133
|
+
reason: `${found.relativePath} leads outside the project, to ${real}, so it is not started.`
|
|
25134
|
+
};
|
|
25135
|
+
}
|
|
25136
|
+
if (!statSync(real).isFile()) {
|
|
25137
|
+
return { ok: false, reason: `${found.relativePath} is not a file, so there is nothing to start.` };
|
|
25138
|
+
}
|
|
25139
|
+
return { ok: true, absolutePath: real, relativePath: found.relativePath };
|
|
25140
|
+
}
|
|
25115
25141
|
|
|
25116
25142
|
// src/process-children.ts
|
|
25117
|
-
import { execFile } from "node:child_process";
|
|
25118
25143
|
import { basename } from "node:path";
|
|
25119
|
-
|
|
25144
|
+
|
|
25145
|
+
// src/spawn.ts
|
|
25146
|
+
import {
|
|
25147
|
+
execFile,
|
|
25148
|
+
execFileSync,
|
|
25149
|
+
spawn,
|
|
25150
|
+
spawnSync
|
|
25151
|
+
} from "node:child_process";
|
|
25152
|
+
function spawnHidden(command, args, options = {}) {
|
|
25153
|
+
return spawn(command, [...args], { ...options, windowsHide: true });
|
|
25154
|
+
}
|
|
25155
|
+
function runHidden(command, args, options = {}) {
|
|
25156
|
+
return new Promise((resolve, reject) => {
|
|
25157
|
+
execFile(command, [...args], { ...options, encoding: "utf8", windowsHide: true }, (error, stdout, stderr) => {
|
|
25158
|
+
if (error !== null) {
|
|
25159
|
+
Object.assign(error, { stdout, stderr });
|
|
25160
|
+
reject(error);
|
|
25161
|
+
return;
|
|
25162
|
+
}
|
|
25163
|
+
resolve({ stdout, stderr });
|
|
25164
|
+
});
|
|
25165
|
+
});
|
|
25166
|
+
}
|
|
25167
|
+
function spawnSyncHidden(command, args, options = {}) {
|
|
25168
|
+
return spawnSync(command, [...args], { ...options, windowsHide: true });
|
|
25169
|
+
}
|
|
25120
25170
|
|
|
25121
25171
|
// src/windows-ask.ts
|
|
25122
|
-
import { spawn } from "node:child_process";
|
|
25123
25172
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
25124
25173
|
import process6 from "node:process";
|
|
25125
25174
|
var SCRIPT = [
|
|
@@ -25168,14 +25217,14 @@ function running() {
|
|
|
25168
25217
|
if (helper?.exitCode === null && helper.signalCode === null) {
|
|
25169
25218
|
return helper;
|
|
25170
25219
|
}
|
|
25171
|
-
const child =
|
|
25220
|
+
const child = spawnHidden("powershell.exe", [
|
|
25172
25221
|
"-NoProfile",
|
|
25173
25222
|
"-NonInteractive",
|
|
25174
25223
|
"-ExecutionPolicy",
|
|
25175
25224
|
"Bypass",
|
|
25176
25225
|
"-EncodedCommand",
|
|
25177
25226
|
Buffer.from(SCRIPT, "utf16le").toString("base64")
|
|
25178
|
-
], {
|
|
25227
|
+
], { cwd: tmpdir2() });
|
|
25179
25228
|
buffered = "";
|
|
25180
25229
|
child.stdout.setEncoding("utf8");
|
|
25181
25230
|
child.stdout.on("data", (chunk) => {
|
|
@@ -25268,7 +25317,6 @@ function askWindows(script, withinMs) {
|
|
|
25268
25317
|
}
|
|
25269
25318
|
|
|
25270
25319
|
// src/process-children.ts
|
|
25271
|
-
var run = promisify(execFile);
|
|
25272
25320
|
async function startTimesOf(pids) {
|
|
25273
25321
|
const began = new Map;
|
|
25274
25322
|
if (pids.length === 0) {
|
|
@@ -25303,10 +25351,9 @@ async function startTimesOf(pids) {
|
|
|
25303
25351
|
return began;
|
|
25304
25352
|
}
|
|
25305
25353
|
function printedBy(command, args, env) {
|
|
25306
|
-
return
|
|
25307
|
-
|
|
25308
|
-
|
|
25309
|
-
});
|
|
25354
|
+
return runHidden(command, args, { timeout: ASK_TIMEOUT_MS, env }).then(({ stdout }) => stdout, (error) => {
|
|
25355
|
+
const printed = error.stdout;
|
|
25356
|
+
return typeof printed === "string" ? printed : "";
|
|
25310
25357
|
});
|
|
25311
25358
|
}
|
|
25312
25359
|
var ASK_TIMEOUT_MS = 15000;
|
|
@@ -25374,10 +25421,18 @@ function linked(tree, child, parent) {
|
|
|
25374
25421
|
}
|
|
25375
25422
|
return from.startedAt >= to.startedAt;
|
|
25376
25423
|
}
|
|
25424
|
+
function isConsoleHost(listed) {
|
|
25425
|
+
const executable = commandWords(listed.command)[0];
|
|
25426
|
+
if (executable === undefined) {
|
|
25427
|
+
return false;
|
|
25428
|
+
}
|
|
25429
|
+
const host = `${process.env["SystemRoot"] ?? "C:\\Windows"}\\System32\\conhost.exe`;
|
|
25430
|
+
return executable.replace(/^\\\?\?\\/, "").replaceAll("/", "\\").toLowerCase() === host.toLowerCase();
|
|
25431
|
+
}
|
|
25377
25432
|
function childrenIn(tree, pid) {
|
|
25378
25433
|
const children = [];
|
|
25379
|
-
for (const child of tree
|
|
25380
|
-
if (linked(tree, child, pid)) {
|
|
25434
|
+
for (const [child, listed] of tree) {
|
|
25435
|
+
if (linked(tree, child, pid) && !isConsoleHost(listed)) {
|
|
25381
25436
|
children.push(child);
|
|
25382
25437
|
}
|
|
25383
25438
|
}
|
|
@@ -25500,7 +25555,7 @@ function listWindows() {
|
|
|
25500
25555
|
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);
|
|
25501
25556
|
}
|
|
25502
25557
|
async function askPosix() {
|
|
25503
|
-
const { stdout } = await
|
|
25558
|
+
const { stdout } = await runHidden("ps", ["-e", "-o", "pid=,ppid=,args="], {
|
|
25504
25559
|
timeout: ASK_TIMEOUT_MS,
|
|
25505
25560
|
maxBuffer: 16 * 1024 * 1024
|
|
25506
25561
|
});
|
|
@@ -25636,7 +25691,7 @@ function parseAnnouncement(file, pid) {
|
|
|
25636
25691
|
};
|
|
25637
25692
|
let announcedAt;
|
|
25638
25693
|
try {
|
|
25639
|
-
announcedAt =
|
|
25694
|
+
announcedAt = statSync2(file).mtimeMs;
|
|
25640
25695
|
} catch {}
|
|
25641
25696
|
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
25642
25697
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
@@ -25758,7 +25813,7 @@ function announcedIn(directory) {
|
|
|
25758
25813
|
entries.push({ file, pid, running });
|
|
25759
25814
|
if (running) {
|
|
25760
25815
|
try {
|
|
25761
|
-
candidates.push({ file, pid, writtenAt:
|
|
25816
|
+
candidates.push({ file, pid, writtenAt: statSync2(file).mtimeMs });
|
|
25762
25817
|
} catch {}
|
|
25763
25818
|
}
|
|
25764
25819
|
}
|
|
@@ -25816,7 +25871,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
25816
25871
|
return;
|
|
25817
25872
|
}
|
|
25818
25873
|
try {
|
|
25819
|
-
if (now -
|
|
25874
|
+
if (now - statSync2(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
25820
25875
|
unlinkSync(path);
|
|
25821
25876
|
}
|
|
25822
25877
|
} catch {}
|
|
@@ -26256,7 +26311,7 @@ function callProgress() {
|
|
|
26256
26311
|
}
|
|
26257
26312
|
|
|
26258
26313
|
// src/class-cache.ts
|
|
26259
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as
|
|
26314
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "node:fs";
|
|
26260
26315
|
import { join as join6, posix } from "node:path";
|
|
26261
26316
|
|
|
26262
26317
|
// src/gdscript-source.ts
|
|
@@ -27233,7 +27288,7 @@ function uncachedClassNote(types) {
|
|
|
27233
27288
|
function cacheWrittenAt(projectPath) {
|
|
27234
27289
|
const cache = join6(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
27235
27290
|
try {
|
|
27236
|
-
return
|
|
27291
|
+
return statSync3(cache).mtimeMs;
|
|
27237
27292
|
} catch {
|
|
27238
27293
|
return null;
|
|
27239
27294
|
}
|
|
@@ -27245,7 +27300,7 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
27245
27300
|
return false;
|
|
27246
27301
|
}
|
|
27247
27302
|
try {
|
|
27248
|
-
return
|
|
27303
|
+
return statSync3(join6(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
27249
27304
|
} catch {
|
|
27250
27305
|
return false;
|
|
27251
27306
|
}
|
|
@@ -27271,8 +27326,40 @@ function unseenByEditor(projectPath, editorHolds) {
|
|
|
27271
27326
|
}
|
|
27272
27327
|
|
|
27273
27328
|
// src/class-note.ts
|
|
27274
|
-
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8
|
|
27329
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8 } from "node:fs";
|
|
27275
27330
|
import { dirname as dirname5, join as join7 } from "node:path";
|
|
27331
|
+
|
|
27332
|
+
// src/whole-file.ts
|
|
27333
|
+
import { renameSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
27334
|
+
var HELD_FILE_MS = 1000;
|
|
27335
|
+
var HELD_FILE_RETRY_MS = 10;
|
|
27336
|
+
var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
27337
|
+
function writeWhole(path, text) {
|
|
27338
|
+
const partial = `${path}.${process.pid}.partial`;
|
|
27339
|
+
try {
|
|
27340
|
+
writeFileSync5(partial, text, "utf8");
|
|
27341
|
+
} catch {
|
|
27342
|
+
return false;
|
|
27343
|
+
}
|
|
27344
|
+
const until = Date.now() + HELD_FILE_MS;
|
|
27345
|
+
for (;; ) {
|
|
27346
|
+
try {
|
|
27347
|
+
renameSync(partial, path);
|
|
27348
|
+
return true;
|
|
27349
|
+
} catch (error) {
|
|
27350
|
+
const code = error.code ?? "";
|
|
27351
|
+
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
27352
|
+
try {
|
|
27353
|
+
rmSync3(partial, { force: true });
|
|
27354
|
+
} catch {}
|
|
27355
|
+
return false;
|
|
27356
|
+
}
|
|
27357
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
27358
|
+
}
|
|
27359
|
+
}
|
|
27360
|
+
}
|
|
27361
|
+
|
|
27362
|
+
// src/class-note.ts
|
|
27276
27363
|
function classNotePath(projectPath) {
|
|
27277
27364
|
return join7(projectPath, ".godot", "gdharness-classes.json");
|
|
27278
27365
|
}
|
|
@@ -27284,21 +27371,36 @@ function readClassNote(projectPath) {
|
|
|
27284
27371
|
return null;
|
|
27285
27372
|
}
|
|
27286
27373
|
const pid = readNumber(fields, "shortListEditorPid");
|
|
27374
|
+
const unfinished = unfinishedScanIn(fields["unseenBeforeAnUnfinishedScan"]);
|
|
27287
27375
|
return {
|
|
27288
27376
|
rebuiltWith: rebuiltWith.map(String),
|
|
27289
|
-
...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {}
|
|
27377
|
+
...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {},
|
|
27378
|
+
...unfinished === null ? {} : { unseenBeforeAnUnfinishedScan: unfinished }
|
|
27290
27379
|
};
|
|
27291
27380
|
} catch {
|
|
27292
27381
|
return null;
|
|
27293
27382
|
}
|
|
27294
27383
|
}
|
|
27384
|
+
function unfinishedScanIn(value) {
|
|
27385
|
+
if (typeof value !== "object" || value === null) {
|
|
27386
|
+
return null;
|
|
27387
|
+
}
|
|
27388
|
+
const fields = asParams(value);
|
|
27389
|
+
const classes = readArray(fields, "classes");
|
|
27390
|
+
if (classes === undefined || !classes.every((name) => typeof name === "string")) {
|
|
27391
|
+
return null;
|
|
27392
|
+
}
|
|
27393
|
+
return { editorPid: readNumber(fields, "editorPid") ?? null, classes: classes.map(String) };
|
|
27394
|
+
}
|
|
27295
27395
|
function writeClassNote(projectPath, note) {
|
|
27396
|
+
const path = classNotePath(projectPath);
|
|
27296
27397
|
try {
|
|
27297
|
-
const path = classNotePath(projectPath);
|
|
27298
27398
|
mkdirSync5(dirname5(path), { recursive: true });
|
|
27299
|
-
|
|
27300
|
-
|
|
27301
|
-
}
|
|
27399
|
+
} catch {
|
|
27400
|
+
return;
|
|
27401
|
+
}
|
|
27402
|
+
writeWhole(path, `${JSON.stringify(note, null, 2)}
|
|
27403
|
+
`);
|
|
27302
27404
|
}
|
|
27303
27405
|
|
|
27304
27406
|
// src/config-pin.ts
|
|
@@ -27329,7 +27431,7 @@ function configDisagrees(projectPath, serverVersion) {
|
|
|
27329
27431
|
}
|
|
27330
27432
|
|
|
27331
27433
|
// src/cut-test-run.ts
|
|
27332
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync10, rmSync as
|
|
27434
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync10, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
27333
27435
|
import { dirname as dirname6, join as join9 } from "node:path";
|
|
27334
27436
|
function keptAt(projectPath) {
|
|
27335
27437
|
return join9(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
@@ -27349,10 +27451,10 @@ function takeCutTestRun(projectPath) {
|
|
|
27349
27451
|
}
|
|
27350
27452
|
try {
|
|
27351
27453
|
const run = JSON.parse(readFileSync10(path, "utf8"));
|
|
27352
|
-
|
|
27454
|
+
rmSync4(path, { force: true });
|
|
27353
27455
|
return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
|
|
27354
27456
|
} catch {
|
|
27355
|
-
|
|
27457
|
+
rmSync4(path, { force: true });
|
|
27356
27458
|
return null;
|
|
27357
27459
|
}
|
|
27358
27460
|
}
|
|
@@ -28172,8 +28274,8 @@ import {
|
|
|
28172
28274
|
openSync,
|
|
28173
28275
|
readFileSync as readFileSync11,
|
|
28174
28276
|
readSync,
|
|
28175
|
-
rmSync as
|
|
28176
|
-
statSync as
|
|
28277
|
+
rmSync as rmSync5,
|
|
28278
|
+
statSync as statSync4,
|
|
28177
28279
|
writeFileSync as writeFileSync7
|
|
28178
28280
|
} from "node:fs";
|
|
28179
28281
|
import { dirname as dirname7, join as join10 } from "node:path";
|
|
@@ -28433,8 +28535,8 @@ function clearEditorLog(projectPath) {
|
|
|
28433
28535
|
try {
|
|
28434
28536
|
const path = editorLogPath(projectPath);
|
|
28435
28537
|
mkdirSync7(dirname7(path), { recursive: true });
|
|
28436
|
-
|
|
28437
|
-
|
|
28538
|
+
rmSync5(path, { force: true });
|
|
28539
|
+
rmSync5(editorLogNotePath(projectPath), { force: true });
|
|
28438
28540
|
} catch {}
|
|
28439
28541
|
}
|
|
28440
28542
|
function writeEditorLogNote(projectPath, pid) {
|
|
@@ -28521,7 +28623,7 @@ function foldProgress(text) {
|
|
|
28521
28623
|
}
|
|
28522
28624
|
var MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
|
|
28523
28625
|
function theFirstOf(path) {
|
|
28524
|
-
const whole =
|
|
28626
|
+
const whole = statSync4(path).size;
|
|
28525
28627
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
28526
28628
|
return { text: readFileSync11(path, "utf8") };
|
|
28527
28629
|
}
|
|
@@ -28679,15 +28781,13 @@ function linesBefore(entries, at, collapsed, wanted) {
|
|
|
28679
28781
|
}
|
|
28680
28782
|
|
|
28681
28783
|
// src/engine-run.ts
|
|
28682
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
28683
28784
|
function runEngine(executable, args, options = {}) {
|
|
28684
28785
|
return new Promise((resolve) => {
|
|
28685
28786
|
const log = new GameLog;
|
|
28686
28787
|
let failure = null;
|
|
28687
28788
|
const signal = callSignal();
|
|
28688
|
-
const child =
|
|
28789
|
+
const child = spawnHidden(executable, args, {
|
|
28689
28790
|
stdio: ["ignore", "pipe", "pipe"],
|
|
28690
|
-
windowsHide: true,
|
|
28691
28791
|
...signal === undefined ? {} : { signal }
|
|
28692
28792
|
});
|
|
28693
28793
|
child.stdout.on("data", (chunk) => {
|
|
@@ -28804,6 +28904,16 @@ function anEditorIsStillComing(listeningSince, launchedEditorIsAlive, now = Date
|
|
|
28804
28904
|
return mayYetConnect(listeningSince, now) || launchedEditorIsAlive;
|
|
28805
28905
|
}
|
|
28806
28906
|
|
|
28907
|
+
class BridgeTimeout extends Error {
|
|
28908
|
+
toolName;
|
|
28909
|
+
waitedMs;
|
|
28910
|
+
constructor(toolName, waitedMs) {
|
|
28911
|
+
super(`Tool ${toolName} timed out after ${waitedMs}ms`);
|
|
28912
|
+
this.toolName = toolName;
|
|
28913
|
+
this.waitedMs = waitedMs;
|
|
28914
|
+
}
|
|
28915
|
+
}
|
|
28916
|
+
|
|
28807
28917
|
class GodotBridge extends EventEmitter {
|
|
28808
28918
|
listeningSince = null;
|
|
28809
28919
|
httpServer = null;
|
|
@@ -29141,7 +29251,7 @@ class GodotBridge extends EventEmitter {
|
|
|
29141
29251
|
return new Promise((resolve, reject) => {
|
|
29142
29252
|
const timeout = setTimeout(() => {
|
|
29143
29253
|
this.pendingRequests.delete(requestId);
|
|
29144
|
-
reject(new
|
|
29254
|
+
reject(new BridgeTimeout(toolName, this.timeoutMs));
|
|
29145
29255
|
}, this.timeoutMs);
|
|
29146
29256
|
this.pendingRequests.set(requestId, {
|
|
29147
29257
|
toolName,
|
|
@@ -29357,13 +29467,11 @@ function getDefaultBridge(ownProject = null) {
|
|
|
29357
29467
|
}
|
|
29358
29468
|
|
|
29359
29469
|
// src/godot-path.ts
|
|
29360
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
29361
29470
|
import { existsSync as existsSync8 } from "node:fs";
|
|
29362
29471
|
import { normalize } from "node:path";
|
|
29363
|
-
import { promisify as promisify2 } from "node:util";
|
|
29364
29472
|
|
|
29365
29473
|
// src/detection.ts
|
|
29366
|
-
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as
|
|
29474
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5 } from "node:fs";
|
|
29367
29475
|
import { homedir as homedir2 } from "node:os";
|
|
29368
29476
|
import { join as join11 } from "node:path";
|
|
29369
29477
|
function resolveHomeDirectory() {
|
|
@@ -29391,7 +29499,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
|
|
|
29391
29499
|
}
|
|
29392
29500
|
const fullPath = join11(directory, name);
|
|
29393
29501
|
try {
|
|
29394
|
-
const stat =
|
|
29502
|
+
const stat = statSync5(fullPath);
|
|
29395
29503
|
if (stat.isFile()) {
|
|
29396
29504
|
matches.push({ name, mtime: stat.mtimeMs });
|
|
29397
29505
|
}
|
|
@@ -29448,8 +29556,6 @@ function godotCandidates(platform, home) {
|
|
|
29448
29556
|
}
|
|
29449
29557
|
|
|
29450
29558
|
// src/godot-path.ts
|
|
29451
|
-
var run2 = promisify2(execFile2);
|
|
29452
|
-
|
|
29453
29559
|
class GodotLocator {
|
|
29454
29560
|
answered = new Map;
|
|
29455
29561
|
found = null;
|
|
@@ -29505,7 +29611,7 @@ class GodotLocator {
|
|
|
29505
29611
|
reason = "does not exist";
|
|
29506
29612
|
} else {
|
|
29507
29613
|
try {
|
|
29508
|
-
await
|
|
29614
|
+
await runHidden(path, ["--version"]);
|
|
29509
29615
|
reason = null;
|
|
29510
29616
|
} catch (error) {
|
|
29511
29617
|
reason = `does not answer --version: ${errorMessage(error)}`;
|
|
@@ -29521,7 +29627,7 @@ import { existsSync as existsSync12, readFileSync as readFileSync14, realpathSyn
|
|
|
29521
29627
|
import { dirname as dirname9, join as join15 } from "node:path";
|
|
29522
29628
|
|
|
29523
29629
|
// src/library-copies.ts
|
|
29524
|
-
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync12, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
29630
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync12, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
29525
29631
|
import { basename as basename2, dirname as dirname8, isAbsolute as isAbsolute3, join as join12 } from "node:path";
|
|
29526
29632
|
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
29527
29633
|
const setAside = [];
|
|
@@ -29535,7 +29641,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
29535
29641
|
if (existsSync9(join12(folder, copy))) {
|
|
29536
29642
|
const aside = join12(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
29537
29643
|
try {
|
|
29538
|
-
|
|
29644
|
+
renameSync2(join12(folder, copy), aside);
|
|
29539
29645
|
setAside.push(aside);
|
|
29540
29646
|
} catch {}
|
|
29541
29647
|
}
|
|
@@ -29603,7 +29709,7 @@ function readOrNull(path) {
|
|
|
29603
29709
|
|
|
29604
29710
|
// src/reimport.ts
|
|
29605
29711
|
import { createHash as createHash2 } from "node:crypto";
|
|
29606
|
-
import { existsSync as existsSync10, readFileSync as readFileSync13, rmSync as
|
|
29712
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, rmSync as rmSync6, statSync as statSync6, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
29607
29713
|
import { basename as basename3, join as join13 } from "node:path";
|
|
29608
29714
|
function forceNextImport(projectPath, resourcePath) {
|
|
29609
29715
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
@@ -29611,10 +29717,10 @@ function forceNextImport(projectPath, resourcePath) {
|
|
|
29611
29717
|
if (!existsSync10(sidecar)) {
|
|
29612
29718
|
return;
|
|
29613
29719
|
}
|
|
29614
|
-
const { atime, mtimeMs } =
|
|
29720
|
+
const { atime, mtimeMs } = statSync6(sidecar);
|
|
29615
29721
|
writeFileSync8(sidecar, readFileSync13(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
29616
29722
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
29617
|
-
|
|
29723
|
+
rmSync6(importRecord(projectPath, resourcePath), { force: true });
|
|
29618
29724
|
}
|
|
29619
29725
|
function importRecord(projectPath, resourcePath) {
|
|
29620
29726
|
const digest = createHash2("md5").update(resourcePath).digest("hex");
|
|
@@ -29622,7 +29728,7 @@ function importRecord(projectPath, resourcePath) {
|
|
|
29622
29728
|
}
|
|
29623
29729
|
|
|
29624
29730
|
// src/scratch.ts
|
|
29625
|
-
import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as
|
|
29731
|
+
import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync7 } from "node:fs";
|
|
29626
29732
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
29627
29733
|
import { join as join14 } from "node:path";
|
|
29628
29734
|
import process7 from "node:process";
|
|
@@ -29681,7 +29787,7 @@ function discard(...paths) {
|
|
|
29681
29787
|
return left;
|
|
29682
29788
|
}
|
|
29683
29789
|
function remove(path) {
|
|
29684
|
-
|
|
29790
|
+
rmSync7(path, SWEPT);
|
|
29685
29791
|
}
|
|
29686
29792
|
function tryAgain(path, attempt) {
|
|
29687
29793
|
if (attempt === 0) {
|
|
@@ -30513,7 +30619,7 @@ function askTheKeeper(address, line, withinMs) {
|
|
|
30513
30619
|
}
|
|
30514
30620
|
|
|
30515
30621
|
// src/lifecycle-log.ts
|
|
30516
|
-
import { appendFileSync, mkdirSync as mkdirSync8, renameSync as
|
|
30622
|
+
import { appendFileSync, mkdirSync as mkdirSync8, renameSync as renameSync3, statSync as statSync7 } from "node:fs";
|
|
30517
30623
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
30518
30624
|
import process8 from "node:process";
|
|
30519
30625
|
function lifecycleLogPath() {
|
|
@@ -30524,9 +30630,9 @@ function writeLifecycle(event, fields = {}) {
|
|
|
30524
30630
|
const path = lifecycleLogPath();
|
|
30525
30631
|
try {
|
|
30526
30632
|
mkdirSync8(dirname10(path), { recursive: true });
|
|
30527
|
-
if ((
|
|
30633
|
+
if ((statSync7(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
30528
30634
|
try {
|
|
30529
|
-
|
|
30635
|
+
renameSync3(path, `${path}.1`);
|
|
30530
30636
|
} catch {}
|
|
30531
30637
|
}
|
|
30532
30638
|
const line = {
|
|
@@ -31385,7 +31491,6 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
31385
31491
|
}
|
|
31386
31492
|
|
|
31387
31493
|
// src/outside.ts
|
|
31388
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
31389
31494
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
31390
31495
|
function changesFrom(env, base) {
|
|
31391
31496
|
const changes = {};
|
|
@@ -31419,9 +31524,8 @@ function readLaunched(printed) {
|
|
|
31419
31524
|
}
|
|
31420
31525
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
31421
31526
|
return await new Promise((resolve) => {
|
|
31422
|
-
const launcher =
|
|
31423
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31424
|
-
windowsHide: true
|
|
31527
|
+
const launcher = spawnHidden(runtime, [keeper, "launch"], {
|
|
31528
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
31425
31529
|
});
|
|
31426
31530
|
sendSpec(launcher, {
|
|
31427
31531
|
command: spec.command,
|
|
@@ -31485,9 +31589,6 @@ function notListeningNote(servers, greetedAgoMs) {
|
|
|
31485
31589
|
}
|
|
31486
31590
|
|
|
31487
31591
|
// src/process-time.ts
|
|
31488
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
31489
|
-
import { promisify as promisify3 } from "node:util";
|
|
31490
|
-
var run3 = promisify3(execFile3);
|
|
31491
31592
|
var ASK_TIMEOUT_MS2 = 6000;
|
|
31492
31593
|
async function cpuSecondsOf(pid) {
|
|
31493
31594
|
try {
|
|
@@ -31502,7 +31603,7 @@ async function secondsOnWindows(pid) {
|
|
|
31502
31603
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
31503
31604
|
}
|
|
31504
31605
|
async function askPosix2(pid) {
|
|
31505
|
-
const { stdout } = await
|
|
31606
|
+
const { stdout } = await runHidden("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
|
|
31506
31607
|
return secondsFromClock(stdout.trim());
|
|
31507
31608
|
}
|
|
31508
31609
|
function secondsFromClock(printed) {
|
|
@@ -31912,7 +32013,7 @@ function setupPromptHandlers(server) {
|
|
|
31912
32013
|
}
|
|
31913
32014
|
|
|
31914
32015
|
// src/rename.ts
|
|
31915
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as
|
|
32016
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
31916
32017
|
import { dirname as dirname12, posix as posix2 } from "node:path";
|
|
31917
32018
|
class Script {
|
|
31918
32019
|
path;
|
|
@@ -32649,12 +32750,12 @@ function applyRewrites(rewrites) {
|
|
|
32649
32750
|
mkdirSync9(dirname12(rewrite.writeTo), { recursive: true });
|
|
32650
32751
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
32651
32752
|
writeFileSync10(staging, rewrite.after, "utf8");
|
|
32652
|
-
|
|
32753
|
+
renameSync4(staging, rewrite.writeTo);
|
|
32653
32754
|
let movedUid = false;
|
|
32654
32755
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
32655
32756
|
unlinkSync3(rewrite.absolute);
|
|
32656
32757
|
if (existsSync15(`${rewrite.absolute}.uid`)) {
|
|
32657
|
-
|
|
32758
|
+
renameSync4(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
32658
32759
|
movedUid = true;
|
|
32659
32760
|
}
|
|
32660
32761
|
}
|
|
@@ -32667,7 +32768,7 @@ function applyRewrites(rewrites) {
|
|
|
32667
32768
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
32668
32769
|
unlinkSync3(rewrite.writeTo);
|
|
32669
32770
|
if (movedUid) {
|
|
32670
|
-
|
|
32771
|
+
renameSync4(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
|
|
32671
32772
|
}
|
|
32672
32773
|
}
|
|
32673
32774
|
} catch {}
|
|
@@ -32677,7 +32778,7 @@ function applyRewrites(rewrites) {
|
|
|
32677
32778
|
}
|
|
32678
32779
|
|
|
32679
32780
|
// src/rename-symbol.ts
|
|
32680
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readdirSync as readdirSync7, realpathSync as realpathSync5, rmSync as
|
|
32781
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readdirSync as readdirSync7, realpathSync as realpathSync5, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
32681
32782
|
import { basename as basename4, dirname as dirname13, join as join19, relative as relative3, resolve as resolve5 } from "node:path";
|
|
32682
32783
|
|
|
32683
32784
|
// src/resources.ts
|
|
@@ -33424,7 +33525,7 @@ function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
|
33424
33525
|
`, "utf8");
|
|
33425
33526
|
const kept = readdirSync7(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
33426
33527
|
for (const old of kept.slice(10)) {
|
|
33427
|
-
|
|
33528
|
+
rmSync8(join19(directory, old), { force: true });
|
|
33428
33529
|
}
|
|
33429
33530
|
return path;
|
|
33430
33531
|
}
|
|
@@ -33447,7 +33548,7 @@ function summaryOf(edits, mentions, rewrites, left) {
|
|
|
33447
33548
|
}
|
|
33448
33549
|
|
|
33449
33550
|
// src/restart-note.ts
|
|
33450
|
-
import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as
|
|
33551
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
|
33451
33552
|
import { dirname as dirname14, join as join20 } from "node:path";
|
|
33452
33553
|
function restartNotePath(projectPath) {
|
|
33453
33554
|
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
@@ -33479,51 +33580,16 @@ function restartOwed(projectPath) {
|
|
|
33479
33580
|
}
|
|
33480
33581
|
function restartSettled(projectPath) {
|
|
33481
33582
|
try {
|
|
33482
|
-
|
|
33583
|
+
rmSync9(restartNotePath(projectPath), { force: true });
|
|
33483
33584
|
} catch (error) {
|
|
33484
33585
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
33485
33586
|
}
|
|
33486
33587
|
}
|
|
33487
33588
|
|
|
33488
33589
|
// src/run-record.ts
|
|
33489
|
-
import { execFile as execFile4 } from "node:child_process";
|
|
33490
33590
|
import { createHash as createHash3 } from "node:crypto";
|
|
33491
|
-
import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, rmSync as rmSync10, statSync as
|
|
33591
|
+
import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, rmSync as rmSync10, statSync as statSync8 } from "node:fs";
|
|
33492
33592
|
import { basename as basename5, dirname as dirname15, join as join21, resolve as resolve6 } from "node:path";
|
|
33493
|
-
import { promisify as promisify4 } from "node:util";
|
|
33494
|
-
|
|
33495
|
-
// src/whole-file.ts
|
|
33496
|
-
import { renameSync as renameSync4, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
33497
|
-
var HELD_FILE_MS = 1000;
|
|
33498
|
-
var HELD_FILE_RETRY_MS = 10;
|
|
33499
|
-
var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
33500
|
-
function writeWhole(path, text) {
|
|
33501
|
-
const partial = `${path}.${process.pid}.partial`;
|
|
33502
|
-
try {
|
|
33503
|
-
writeFileSync13(partial, text, "utf8");
|
|
33504
|
-
} catch {
|
|
33505
|
-
return false;
|
|
33506
|
-
}
|
|
33507
|
-
const until = Date.now() + HELD_FILE_MS;
|
|
33508
|
-
for (;; ) {
|
|
33509
|
-
try {
|
|
33510
|
-
renameSync4(partial, path);
|
|
33511
|
-
return true;
|
|
33512
|
-
} catch (error) {
|
|
33513
|
-
const code = error.code ?? "";
|
|
33514
|
-
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
33515
|
-
try {
|
|
33516
|
-
rmSync9(partial, { force: true });
|
|
33517
|
-
} catch {}
|
|
33518
|
-
return false;
|
|
33519
|
-
}
|
|
33520
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
33521
|
-
}
|
|
33522
|
-
}
|
|
33523
|
-
}
|
|
33524
|
-
|
|
33525
|
-
// src/run-record.ts
|
|
33526
|
-
var execFileAsync = promisify4(execFile4);
|
|
33527
33593
|
function runsDirectory() {
|
|
33528
33594
|
return join21(runtimeDirectory(), "runs");
|
|
33529
33595
|
}
|
|
@@ -33719,7 +33785,7 @@ async function runningAs(pid) {
|
|
|
33719
33785
|
const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
33720
33786
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
33721
33787
|
}
|
|
33722
|
-
const args = (await
|
|
33788
|
+
const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
|
|
33723
33789
|
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
33724
33790
|
} catch {
|
|
33725
33791
|
return null;
|
|
@@ -33728,7 +33794,7 @@ async function runningAs(pid) {
|
|
|
33728
33794
|
async function listeningPid(port) {
|
|
33729
33795
|
try {
|
|
33730
33796
|
if (process.platform === "win32") {
|
|
33731
|
-
const { stdout } = await
|
|
33797
|
+
const { stdout } = await runHidden("netstat", ["-ano"], { timeout: 15000 });
|
|
33732
33798
|
return listeningPidInNetstat(stdout, port);
|
|
33733
33799
|
}
|
|
33734
33800
|
for (const [command, args, pattern] of [
|
|
@@ -33736,7 +33802,7 @@ async function listeningPid(port) {
|
|
|
33736
33802
|
["ss", ["-ltnpH", `sport = :${port}`], /pid=(\d+)/]
|
|
33737
33803
|
]) {
|
|
33738
33804
|
try {
|
|
33739
|
-
const said = (await
|
|
33805
|
+
const said = (await runHidden(command, [...args], { timeout: 15000 })).stdout.trim();
|
|
33740
33806
|
const found = pattern.exec(said);
|
|
33741
33807
|
if (found?.[1] !== undefined) {
|
|
33742
33808
|
return Number(found[1]);
|
|
@@ -33781,7 +33847,7 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
33781
33847
|
return Number.isNaN(when) ? null : when;
|
|
33782
33848
|
}
|
|
33783
33849
|
try {
|
|
33784
|
-
const said = (await
|
|
33850
|
+
const said = (await runHidden("ps", ["-p", String(pid), "-o", "lstart="], { timeout: 15000 })).stdout.trim();
|
|
33785
33851
|
const when = Date.parse(said);
|
|
33786
33852
|
return said === "" || Number.isNaN(when) ? null : when;
|
|
33787
33853
|
} catch {
|
|
@@ -33790,10 +33856,8 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
33790
33856
|
}
|
|
33791
33857
|
async function windowsImage(pid) {
|
|
33792
33858
|
try {
|
|
33793
|
-
const { stdout: csv } = await
|
|
33794
|
-
|
|
33795
|
-
timeout: 15000,
|
|
33796
|
-
windowsHide: true
|
|
33859
|
+
const { stdout: csv } = await runHidden("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
33860
|
+
timeout: 15000
|
|
33797
33861
|
});
|
|
33798
33862
|
const image = /^"([^"]+)"/.exec(csv.trim())?.[1];
|
|
33799
33863
|
return image === undefined ? { why: "named no process under that number" } : { image };
|
|
@@ -33874,7 +33938,7 @@ function clearRunRecord(projectPath) {
|
|
|
33874
33938
|
}
|
|
33875
33939
|
function ageMs(path, now) {
|
|
33876
33940
|
try {
|
|
33877
|
-
return now -
|
|
33941
|
+
return now - statSync8(path).mtimeMs;
|
|
33878
33942
|
} catch {
|
|
33879
33943
|
return 0;
|
|
33880
33944
|
}
|
|
@@ -35138,6 +35202,11 @@ var TOOL_SPECS = [
|
|
|
35138
35202
|
ops: ["start"],
|
|
35139
35203
|
description: "start: run the game with no sound out of the machine, by starting the engine with --audio-driver Dummy: every stream still plays and every playback position still advances, so what is playing can still be asked. The editor takes no engine flags for a game it plays, so a silent windowed run is started by this server, whose keeper is its debugger rather than the editor. A headless run, check's included, is silent already. Default false, or true when GDHARNESS_SILENT is set in the server's environment to anything but 0 or false; silent: false then plays through the editor as before."
|
|
35140
35204
|
},
|
|
35205
|
+
executable: {
|
|
35206
|
+
type: "string",
|
|
35207
|
+
ops: ["start"],
|
|
35208
|
+
description: "start: an exported build to run instead of the project, as a path inside the project such as export/windows/game.exe, so what a player runs can be measured. It takes args, engineArgs, savesIn, env, headless and visible as the project does, goes on the hidden desktop the same way, and is ended by stop, but takes no scene (its pack holds the main scene) and no profile, and has no debugger: a release build quits when given one. Its runtime is not waited for."
|
|
35209
|
+
},
|
|
35141
35210
|
savesIn: {
|
|
35142
35211
|
type: "string",
|
|
35143
35212
|
ops: ["start", "check"],
|
|
@@ -35293,7 +35362,7 @@ var TOOL_SPECS = [
|
|
|
35293
35362
|
name: "editor_rescan",
|
|
35294
35363
|
title: "Rescan the project",
|
|
35295
35364
|
effect: "additive",
|
|
35296
|
-
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it, and the answer says how long under waitedForEditorScanMs. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
|
|
35365
|
+
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it, and the answer says how long under waitedForEditorScanMs. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
|
|
35297
35366
|
parameters: {
|
|
35298
35367
|
projectPath: PROJECT_PATH2,
|
|
35299
35368
|
timeoutMs: {
|
|
@@ -35371,13 +35440,13 @@ var TOOL_SPECS = [
|
|
|
35371
35440
|
pid: RUNNING_PID,
|
|
35372
35441
|
nodePath: {
|
|
35373
35442
|
type: "string",
|
|
35374
|
-
ops: ["tree", "text", "find", "rect", "property"],
|
|
35375
|
-
description:
|
|
35443
|
+
ops: ["tree", "text", "find", "rect", "property", "accessibility"],
|
|
35444
|
+
description: 'tree, find, text: where to start, default /root. rect: the node to place. property: the node to read, or an engine singleton by name as runtime_invoke takes them, "Engine" with property "get_frames_drawn()". accessibility: only the elements that node and those below it made, whether as node or part_of, each window keeping the outermost of them with what they hold, and matched counting them. What a ScrollContainer holds out of its sight is not in the tree until it is scrolled to, so an element missing under one may be scrolled away rather than hidden.'
|
|
35376
35445
|
},
|
|
35377
35446
|
property: {
|
|
35378
35447
|
type: "string",
|
|
35379
35448
|
ops: ["property", "find"],
|
|
35380
|
-
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control\'s theme is read where it is drawn: \'get_theme_stylebox("panel"):content_margin_top\' or \'get_theme_constant("separation")\'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
|
|
35449
|
+
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A vector, a colour, a rectangle or a transform is stepped into by its components as GDScript names them, so one axis is one number: "size:x", "global_position:y", "modulate:a", "global_transform:origin:z"; a name the value does not have is refused, naming the ones it has. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control\'s theme is read where it is drawn: \'get_theme_stylebox("panel"):content_margin_top\' or \'get_theme_constant("separation")\'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
|
|
35381
35450
|
},
|
|
35382
35451
|
depth: {
|
|
35383
35452
|
type: "integer",
|
|
@@ -35393,8 +35462,8 @@ var TOOL_SPECS = [
|
|
|
35393
35462
|
properties: {
|
|
35394
35463
|
type: "array",
|
|
35395
35464
|
items: { type: "string" },
|
|
35396
|
-
ops: ["tree"],
|
|
35397
|
-
description:
|
|
35465
|
+
ops: ["tree", "accessibility"],
|
|
35466
|
+
description: 'tree: the properties to read off each node, by name, so the text of every label and button under a panel is one call: a node that has not got one leaves it out. Colons read through what a node holds, as property does for find. accessibility: the fields to keep on each element, such as ["role", "name", "node"], leaving out the rest (bounds, actions, tooltip); children are always kept.'
|
|
35398
35467
|
},
|
|
35399
35468
|
className: {
|
|
35400
35469
|
type: "string",
|
|
@@ -35410,15 +35479,15 @@ var TOOL_SPECS = [
|
|
|
35410
35479
|
group: { type: "string", ops: ["find"], description: "find: a group the node is in." },
|
|
35411
35480
|
says: {
|
|
35412
35481
|
type: "string",
|
|
35413
|
-
ops: ["find"],
|
|
35414
|
-
description: 'find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively and with a typed space matching any space the game draws (a non-breaking one included), which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern, and a glob that finds nothing says under note how many nodes it would find open at both ends. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Alternatives separated by | match when any one of them does, each by these rules ("*won*|*lost*"); write \\| for a bar that is part of the words.'
|
|
35482
|
+
ops: ["find", "accessibility"],
|
|
35483
|
+
description: 'accessibility: only the elements whose name or value says these words, matched as find matches them, with what they hold. find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively and with a typed space matching any space the game draws (a non-breaking one included), which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern, and a glob that finds nothing says under note how many nodes it would find open at both ends. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Alternatives separated by | match when any one of them does, each by these rules ("*won*|*lost*"); write \\| for a bar that is part of the words.'
|
|
35415
35484
|
},
|
|
35416
35485
|
limit: {
|
|
35417
35486
|
type: "integer",
|
|
35418
35487
|
minimum: 1,
|
|
35419
35488
|
maximum: 5000,
|
|
35420
|
-
ops: ["find", "text"],
|
|
35421
|
-
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with omitted saying how many lines that left behind. At most 5000 for either. A screen whose dialog sits under a long list is a screen read with omitted greater than zero, so raise this or point root at the dialog rather than reading the answer as what is on screen."
|
|
35489
|
+
ops: ["find", "text", "accessibility"],
|
|
35490
|
+
description: "find: the most nodes to answer with, default 100. text: the most lines, default 500, with omitted saying how many lines that left behind. accessibility: the most elements below the windows, depth first, with omitted counting the rest; default all. At most 5000 for either. A screen whose dialog sits under a long list is a screen read with omitted greater than zero, so raise this or point root at the dialog rather than reading the answer as what is on screen."
|
|
35422
35491
|
},
|
|
35423
35492
|
includeHidden: {
|
|
35424
35493
|
type: "boolean",
|
|
@@ -35467,7 +35536,7 @@ var TOOL_SPECS = [
|
|
|
35467
35536
|
},
|
|
35468
35537
|
accessibility: {
|
|
35469
35538
|
effect: "read",
|
|
35470
|
-
summary: "what the game tells a screen reader, read through UI Automation as a Windows screen reader reads it, under windows: one tree per window, each element with its role (text for static text, button, check_box, group for a control holding others), name, value, description, placeholder, bounds in the window's pixels, the actions a reader is offered (click, focus, toggle, set_value, select, expand_collapse, scroll_into_view), and focused on the one with the focus. node is the
|
|
35539
|
+
summary: "what the game tells a screen reader, read through UI Automation as a Windows screen reader reads it, under windows: one tree per window, each element with its role (text for static text, button, check_box, group for a control holding others), name, value, description, placeholder, bounds in the window's pixels, the actions a reader is offered (click, focus, toggle, set_value, select, expand_collapse, scroll_into_view), and focused on the one with the focus. node is the node that made the element; an element a node made as a part of itself has part_of, the node drawn around it that holds its words, or part_of_candidates where several are, and part: announcement marks the text a window announces through. An empty name is read as nothing: static text takes it from accessibility_update_set_value, and _note counts the text elements on screen with none. tooltip is the node's tooltip_text, which a Windows reader is not told. With nothing focused, the engine calls GdharnessRuntime focused. Needs a windowed run on Windows started with engineArgs [\"--accessibility\", \"always\"] or accessibility_support at Always Active, since the engine builds the tree only when asked; reading takes a second or two",
|
|
35471
35540
|
requires: []
|
|
35472
35541
|
}
|
|
35473
35542
|
},
|
|
@@ -35476,19 +35545,19 @@ var TOOL_SPECS = [
|
|
|
35476
35545
|
{
|
|
35477
35546
|
name: "runtime_invoke",
|
|
35478
35547
|
title: "Set or call in the running game",
|
|
35479
|
-
description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. Errors and warnings the engine reported while a called method ran come back under errors, the first 20, with errors_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
|
|
35548
|
+
description: "Sets a property or calls a method on a node, or on one of the engine singletons nodePath names, in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. Errors and warnings the engine reported while a called method ran come back under errors, the first 20, with errors_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
|
|
35480
35549
|
parameters: {
|
|
35481
35550
|
projectPath: RUNNING_PROJECT_PATH,
|
|
35482
35551
|
pid: RUNNING_PID,
|
|
35483
35552
|
nodePath: {
|
|
35484
35553
|
type: "string",
|
|
35485
35554
|
ops: ["set", "call"],
|
|
35486
|
-
description: 'Absolute node path, such as "/root/Main/Player".'
|
|
35555
|
+
description: 'Absolute node path, such as "/root/Main/Player", or the name of an engine singleton: Engine, Performance, RenderingServer, AudioServer, PhysicsServer2D, PhysicsServer3D, NavigationServer2D, NavigationServer3D, Time or TranslationServer, so "RenderingServer" with method viewport_get_measured_render_time_cpu reads what the engine measures. The other singletons are refused: OS runs programs, DisplayServer switches the keyboard layout and Input moves the real pointer.'
|
|
35487
35556
|
},
|
|
35488
35557
|
property: {
|
|
35489
35558
|
type: "string",
|
|
35490
35559
|
ops: ["set"],
|
|
35491
|
-
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a reading method returned, as runtime_inspect property describes. A map keyed by numbers is stepped into by the number, "_game:units:3". The answer reads back off the same holder, and a write that leaves the property as it was is refused and says what it still holds: the engine not taking it and a setter keeping the value look the same, so the refusal names neither. A write of the value the property already holds, at its own precision, as a float given in 64 bits to a property keeping 32, is not refused: it answers unchanged: true.'
|
|
35560
|
+
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a reading method returned, as runtime_inspect property describes. A map keyed by numbers is stepped into by the number, "_game:units:3". A component of a vector, a colour or a transform, "position:x", writes that one alone: the value holding it is a copy, so it is put back where it was read from, setter and all, and a component of what a call returned is refused, since the game keeps that copy nowhere. The answer reads back off the same holder, and a write that leaves the property as it was is refused and says what it still holds: the engine not taking it and a setter keeping the value look the same, so the refusal names neither. A write of the value the property already holds, at its own precision, as a float given in 64 bits to a property keeping 32, is not refused: it answers unchanged: true.'
|
|
35492
35561
|
},
|
|
35493
35562
|
value: {
|
|
35494
35563
|
ops: ["set"],
|
|
@@ -35621,6 +35690,13 @@ var TOOL_SPECS = [
|
|
|
35621
35690
|
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
35622
35691
|
description: "action, key, mouse_click, joypad_button: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held. A wheel button held down is still a whole step, since a wheel is never held."
|
|
35623
35692
|
},
|
|
35693
|
+
holdFrames: {
|
|
35694
|
+
type: "integer",
|
|
35695
|
+
minimum: 1,
|
|
35696
|
+
maximum: 600,
|
|
35697
|
+
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
35698
|
+
description: "action, key, mouse_click, joypad_button: hold a whole press down for this many process frames and then let go, both inside the game, so a pour, a charge or a long press runs to a set length. A press, a frames wait and a release sent as three calls hold it for the time between the calls as well. The answer says held_frames. Not with pressed, and not on a wheel step. Default 1."
|
|
35699
|
+
},
|
|
35624
35700
|
strength: {
|
|
35625
35701
|
type: "number",
|
|
35626
35702
|
minimum: 0,
|
|
@@ -35769,7 +35845,7 @@ var TOOL_SPECS = [
|
|
|
35769
35845
|
property: {
|
|
35770
35846
|
type: "string",
|
|
35771
35847
|
ops: ["until"],
|
|
35772
|
-
description: 'until: the property name, or a colon path through what the node holds, "_game:run:day", read the way runtime_inspect property reads one, a step written as a call included. Walked again on every frame, so a holder the game replaces while the wait is on is followed.'
|
|
35848
|
+
description: 'until: the property name, or a colon path through what the node holds, "_game:run:day", read the way runtime_inspect property reads one, a step written as a call included, and one axis of a vector as well: "global_position:x" with compare ">=" stops once a thing has travelled far enough. Walked again on every frame, so a holder the game replaces while the wait is on is followed.'
|
|
35773
35849
|
},
|
|
35774
35850
|
value: {
|
|
35775
35851
|
ops: ["until"],
|
|
@@ -35954,7 +36030,6 @@ function serverInstructions() {
|
|
|
35954
36030
|
}
|
|
35955
36031
|
|
|
35956
36032
|
// src/setup.ts
|
|
35957
|
-
import { spawnSync } from "node:child_process";
|
|
35958
36033
|
import { createHash as createHash4 } from "node:crypto";
|
|
35959
36034
|
import {
|
|
35960
36035
|
cpSync,
|
|
@@ -35964,8 +36039,8 @@ import {
|
|
|
35964
36039
|
readFileSync as readFileSync21,
|
|
35965
36040
|
rmdirSync,
|
|
35966
36041
|
rmSync as rmSync11,
|
|
35967
|
-
statSync as
|
|
35968
|
-
writeFileSync as
|
|
36042
|
+
statSync as statSync9,
|
|
36043
|
+
writeFileSync as writeFileSync13
|
|
35969
36044
|
} from "node:fs";
|
|
35970
36045
|
import { dirname as dirname16, join as join23 } from "node:path";
|
|
35971
36046
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -35992,7 +36067,7 @@ function addonsDigest(names, from) {
|
|
|
35992
36067
|
}
|
|
35993
36068
|
const path = join23(directory, name);
|
|
35994
36069
|
const inside = `${relative}/${name}`;
|
|
35995
|
-
if (
|
|
36070
|
+
if (statSync9(path).isDirectory()) {
|
|
35996
36071
|
walk(path, inside);
|
|
35997
36072
|
} else {
|
|
35998
36073
|
hash.update(`${inside}
|
|
@@ -36086,7 +36161,7 @@ function trackedByGit(projectPath, wanted) {
|
|
|
36086
36161
|
if (wanted.length === 0) {
|
|
36087
36162
|
return new Set;
|
|
36088
36163
|
}
|
|
36089
|
-
const asked =
|
|
36164
|
+
const asked = spawnSyncHidden("git", ["ls-files", "-z", "--", ...wanted], {
|
|
36090
36165
|
cwd: projectPath,
|
|
36091
36166
|
encoding: "utf8"
|
|
36092
36167
|
});
|
|
@@ -36183,7 +36258,6 @@ function argumentShape(schema) {
|
|
|
36183
36258
|
// src/server.ts
|
|
36184
36259
|
var UPDATE_NOTICE_EVERY = 500;
|
|
36185
36260
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
36186
|
-
var run4 = promisify5(execFile5);
|
|
36187
36261
|
var __dirname2 = dirname17(fileURLToPath4(import.meta.url));
|
|
36188
36262
|
var PARENT_ASKED_AFTER_MS = 5000;
|
|
36189
36263
|
function editorRestartTimeoutMs() {
|
|
@@ -36281,6 +36355,9 @@ function captureDetail(payload) {
|
|
|
36281
36355
|
function patienceForFrames(frames, atLeast) {
|
|
36282
36356
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
36283
36357
|
}
|
|
36358
|
+
function withDefined(fields) {
|
|
36359
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
36360
|
+
}
|
|
36284
36361
|
var ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.";
|
|
36285
36362
|
function longerWaitNote(tool, op, waitedMs) {
|
|
36286
36363
|
const spec = TOOL_SPECS.find((one) => one.name === tool);
|
|
@@ -36749,6 +36826,31 @@ function notRunNote(notRun, failFast) {
|
|
|
36749
36826
|
const cases = `${notRun} case${notRun === 1 ? "" : "s"}`;
|
|
36750
36827
|
return failFast ? `This run stopped at the first failure in each suite it failed in, so ${cases} never ran and count as neither passed nor failed. Leave failFast out to run every case.` : `${cases} counted in ${notRun === 1 ? "its suite" : "their suites"} never ran and count as neither passed nor failed. failFast was not set, so it was not that: the suites below with notRun are the ones to read.`;
|
|
36751
36828
|
}
|
|
36829
|
+
function exportPresetRefusal(projectPath, preset) {
|
|
36830
|
+
let text;
|
|
36831
|
+
try {
|
|
36832
|
+
text = readFileSync22(join24(projectPath, "export_presets.cfg"), "utf8");
|
|
36833
|
+
} catch {
|
|
36834
|
+
return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. The editor writes the file when a preset is added in Project > Export.`;
|
|
36835
|
+
}
|
|
36836
|
+
const names = [];
|
|
36837
|
+
let inPreset = false;
|
|
36838
|
+
for (const line of text.split(/\r?\n/)) {
|
|
36839
|
+
const section = /^\s*\[([^\]]+)\]/.exec(line)?.[1];
|
|
36840
|
+
if (section !== undefined) {
|
|
36841
|
+
inPreset = /^preset\.\d+$/.test(section.trim());
|
|
36842
|
+
continue;
|
|
36843
|
+
}
|
|
36844
|
+
const named = inPreset ? /^\s*name\s*=\s*"((?:[^"\\]|\\.)*)"/.exec(line)?.[1] : undefined;
|
|
36845
|
+
if (named !== undefined) {
|
|
36846
|
+
names.push(named.replace(/\\(.)/g, "$1"));
|
|
36847
|
+
}
|
|
36848
|
+
}
|
|
36849
|
+
if (names.includes(preset)) {
|
|
36850
|
+
return null;
|
|
36851
|
+
}
|
|
36852
|
+
return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. The editor adds one in Project > Export.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
|
|
36853
|
+
}
|
|
36752
36854
|
function exportAnswer(ending, asked, before, after) {
|
|
36753
36855
|
const { log } = ending;
|
|
36754
36856
|
const written = after !== null && after !== before;
|
|
@@ -37375,7 +37477,7 @@ function scriptsToDiagnose(projectPath, given) {
|
|
|
37375
37477
|
var EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
37376
37478
|
function isDirectory(path) {
|
|
37377
37479
|
try {
|
|
37378
|
-
return
|
|
37480
|
+
return statSync10(path).isDirectory();
|
|
37379
37481
|
} catch {
|
|
37380
37482
|
return false;
|
|
37381
37483
|
}
|
|
@@ -38245,7 +38347,15 @@ class GodotServer {
|
|
|
38245
38347
|
property: readNonEmptyString(args, "property") ?? ""
|
|
38246
38348
|
}, { tool: "runtime_inspect", op, patience });
|
|
38247
38349
|
case "accessibility":
|
|
38248
|
-
return await this.handleRuntimeCommand("get_accessibility",
|
|
38350
|
+
return await this.handleRuntimeCommand("get_accessibility", {
|
|
38351
|
+
...whichGame(args),
|
|
38352
|
+
...withDefined({
|
|
38353
|
+
node_path: readNonEmptyString(args, "nodePath"),
|
|
38354
|
+
says: readNonEmptyString(args, "says"),
|
|
38355
|
+
properties: readStringArray(args, "properties"),
|
|
38356
|
+
limit: readPositiveNumber(args, "limit")
|
|
38357
|
+
})
|
|
38358
|
+
}, {
|
|
38249
38359
|
tool: "runtime_inspect",
|
|
38250
38360
|
op,
|
|
38251
38361
|
patience: readPositiveNumber(args, "timeoutMs") ?? Math.max(30000, this.runtimeTimeoutMs())
|
|
@@ -38296,7 +38406,14 @@ class GodotServer {
|
|
|
38296
38406
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
38297
38407
|
}, { tool: "runtime_input", op });
|
|
38298
38408
|
}
|
|
38299
|
-
|
|
38409
|
+
{
|
|
38410
|
+
const holding = readPositiveNumber(args, "holdFrames");
|
|
38411
|
+
return await this.handleRuntimeCommand(`inject_${op}`, args, {
|
|
38412
|
+
tool: "runtime_input",
|
|
38413
|
+
op,
|
|
38414
|
+
...holding === undefined ? {} : { patience: patienceForFrames(holding, this.runtimeTimeoutMs()) }
|
|
38415
|
+
});
|
|
38416
|
+
}
|
|
38300
38417
|
case "runtime_wait":
|
|
38301
38418
|
return await this.handleRuntimeWait(op, args);
|
|
38302
38419
|
case "debug_breakpoint": {
|
|
@@ -39079,6 +39196,10 @@ class GodotServer {
|
|
|
39079
39196
|
if (!output.ok) {
|
|
39080
39197
|
return this.createErrorResponse(output.reason, PATH_SOLUTIONS);
|
|
39081
39198
|
}
|
|
39199
|
+
const noPreset = exportPresetRefusal(project.value.path, preset);
|
|
39200
|
+
if (noPreset !== null) {
|
|
39201
|
+
return this.createErrorResponse(noPreset, ["project_export list reads the presets the project has"]);
|
|
39202
|
+
}
|
|
39082
39203
|
const engine = await this.engine();
|
|
39083
39204
|
if (!engine.ok) {
|
|
39084
39205
|
return engine.response;
|
|
@@ -39102,7 +39223,7 @@ class GodotServer {
|
|
|
39102
39223
|
output.absolutePath
|
|
39103
39224
|
];
|
|
39104
39225
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
39105
|
-
const writtenAt = () => existsSync18(output.absolutePath) ?
|
|
39226
|
+
const writtenAt = () => existsSync18(output.absolutePath) ? statSync10(output.absolutePath).mtimeMs : null;
|
|
39106
39227
|
const before = writtenAt();
|
|
39107
39228
|
const freed = freeLibraryCopies(project.value.path);
|
|
39108
39229
|
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
@@ -39447,7 +39568,7 @@ class GodotServer {
|
|
|
39447
39568
|
}
|
|
39448
39569
|
async godotVersion(godotPath) {
|
|
39449
39570
|
try {
|
|
39450
|
-
const { stdout } = await
|
|
39571
|
+
const { stdout } = await runHidden(godotPath, ["--version"], { timeout: 1e4 });
|
|
39451
39572
|
this.lastGodotVersion = stdout.trim();
|
|
39452
39573
|
return this.lastGodotVersion;
|
|
39453
39574
|
} catch (error) {
|
|
@@ -39508,6 +39629,10 @@ class GodotServer {
|
|
|
39508
39629
|
return markIfStale(answer, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
|
|
39509
39630
|
}
|
|
39510
39631
|
async diagnosticsOf(args) {
|
|
39632
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
39633
|
+
if (waited !== null) {
|
|
39634
|
+
return { refusal: waited, perScript: false };
|
|
39635
|
+
}
|
|
39511
39636
|
const answer = await this.handleLSP("lsp_get_diagnostics", args);
|
|
39512
39637
|
const payload = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
39513
39638
|
const refused = this.languageServerRefusal(payload, "Diagnostics");
|
|
@@ -39592,9 +39717,34 @@ class GodotServer {
|
|
|
39592
39717
|
const recorded = readClassNote(projectPath)?.shortListEditorPid;
|
|
39593
39718
|
return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
|
|
39594
39719
|
}
|
|
39720
|
+
unseenBeforeAnUnfinishedScan(projectPath) {
|
|
39721
|
+
const noted = readClassNote(projectPath)?.unseenBeforeAnUnfinishedScan;
|
|
39722
|
+
if (noted === undefined || noted.editorPid !== (this.godotBridge.getStatus().editorPid ?? null)) {
|
|
39723
|
+
return [];
|
|
39724
|
+
}
|
|
39725
|
+
return [...noted.classes];
|
|
39726
|
+
}
|
|
39727
|
+
noteUnseenBeforeAnUnfinishedScan(projectPath, classes) {
|
|
39728
|
+
const note = readClassNote(projectPath);
|
|
39729
|
+
const held = note?.unseenBeforeAnUnfinishedScan;
|
|
39730
|
+
if (classes.length === 0 && held === undefined) {
|
|
39731
|
+
return;
|
|
39732
|
+
}
|
|
39733
|
+
const { unseenBeforeAnUnfinishedScan: _replaced, ...rest } = note ?? { rebuiltWith: [] };
|
|
39734
|
+
writeClassNote(projectPath, {
|
|
39735
|
+
...rest,
|
|
39736
|
+
...classes.length === 0 ? {} : {
|
|
39737
|
+
unseenBeforeAnUnfinishedScan: {
|
|
39738
|
+
editorPid: this.godotBridge.getStatus().editorPid ?? null,
|
|
39739
|
+
classes: [...classes].sort()
|
|
39740
|
+
}
|
|
39741
|
+
}
|
|
39742
|
+
});
|
|
39743
|
+
}
|
|
39595
39744
|
noteAShortListEditor(projectPath) {
|
|
39596
39745
|
const note = readClassNote(projectPath);
|
|
39597
39746
|
writeClassNote(projectPath, {
|
|
39747
|
+
...note,
|
|
39598
39748
|
rebuiltWith: note?.rebuiltWith ?? [],
|
|
39599
39749
|
shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
|
|
39600
39750
|
});
|
|
@@ -39619,7 +39769,17 @@ class GodotServer {
|
|
|
39619
39769
|
async handleLSP(toolName, args) {
|
|
39620
39770
|
return handleLSPTool(await this.lsp(), toolName, args);
|
|
39621
39771
|
}
|
|
39772
|
+
async waitForTheEditorOpenedHere(args, instead) {
|
|
39773
|
+
if (this.godotBridge.isConnected() || this.launchedEditor === null || !await this.launchedEditorIsUp()) {
|
|
39774
|
+
return null;
|
|
39775
|
+
}
|
|
39776
|
+
return await this.noEditorYet(args, instead);
|
|
39777
|
+
}
|
|
39622
39778
|
async askScriptInfo(toolName, asked, args) {
|
|
39779
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
39780
|
+
if (waited !== null) {
|
|
39781
|
+
return waited;
|
|
39782
|
+
}
|
|
39623
39783
|
const answer = await this.handleLSP(toolName, args);
|
|
39624
39784
|
const refused = this.languageServerRefusal(asParams(JSON.parse(answer.content[0]?.text ?? "{}")), asked);
|
|
39625
39785
|
return refused === null ? answer : refused.refusal;
|
|
@@ -40345,6 +40505,17 @@ class GodotServer {
|
|
|
40345
40505
|
if (sceneToRun && !sceneToRun.ok) {
|
|
40346
40506
|
return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
|
|
40347
40507
|
}
|
|
40508
|
+
const build = readNonEmptyString(args, "executable");
|
|
40509
|
+
const exported = build === undefined ? null : exportedBuild(project.value.path, build);
|
|
40510
|
+
if (exported !== null && !exported.ok) {
|
|
40511
|
+
return this.createErrorResponse(exported.reason, PATH_SOLUTIONS);
|
|
40512
|
+
}
|
|
40513
|
+
if (exported !== null && scene !== undefined) {
|
|
40514
|
+
return this.createErrorResponse("An exported build runs the main scene its pack holds, and takes no scene: leave scene out, or run the project without executable.");
|
|
40515
|
+
}
|
|
40516
|
+
if (exported !== null && readBoolean(args, "profile") === true) {
|
|
40517
|
+
return this.createErrorResponse("A profile is read through the debugger, and an exported build is started without one, since a release build quits when given --remote-debug. Profile the project without executable.");
|
|
40518
|
+
}
|
|
40348
40519
|
const given = this.gameArguments(args);
|
|
40349
40520
|
if (!given.ok) {
|
|
40350
40521
|
return given.response;
|
|
@@ -40361,13 +40532,13 @@ class GodotServer {
|
|
|
40361
40532
|
if (!asked.ok) {
|
|
40362
40533
|
return asked.response;
|
|
40363
40534
|
}
|
|
40364
|
-
if (!sceneToRun && !hasMainScene(project.value.file)) {
|
|
40535
|
+
if (exported === null && !sceneToRun && !hasMainScene(project.value.file)) {
|
|
40365
40536
|
return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
|
|
40366
40537
|
"Pass scene to run one scene",
|
|
40367
40538
|
"Choose the main scene with project_settings set_main_scene"
|
|
40368
40539
|
]);
|
|
40369
40540
|
}
|
|
40370
|
-
const engine = await this.engine();
|
|
40541
|
+
const engine = exported?.ok === true ? { ok: true, value: exported.absolutePath } : await this.engine();
|
|
40371
40542
|
if (!engine.ok) {
|
|
40372
40543
|
return engine.response;
|
|
40373
40544
|
}
|
|
@@ -40405,11 +40576,11 @@ class GodotServer {
|
|
|
40405
40576
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
40406
40577
|
const silent = resolveSilent(args["silent"], process.env) && !headless;
|
|
40407
40578
|
const profiled = readBoolean(args, "profile") === true;
|
|
40408
|
-
if (this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && flags.value.length === 0 && !needsItsOwnProcess(asked.value) && !silent) {
|
|
40579
|
+
if (exported === null && this.godotBridge.isConnected() && editorWouldPlay && given.value.length === 0 && flags.value.length === 0 && !needsItsOwnProcess(asked.value) && !silent) {
|
|
40409
40580
|
return await this.playThroughEditor(sceneArgument, refreshed.value, scanned, project.value.path, alreadyPlaying, runtimeWaitMs, ended, profiled);
|
|
40410
40581
|
}
|
|
40411
40582
|
const cmdArgs = runArguments({
|
|
40412
|
-
projectPath: project.value.path,
|
|
40583
|
+
projectPath: exported === null ? project.value.path : null,
|
|
40413
40584
|
headless,
|
|
40414
40585
|
scene: sceneArgument,
|
|
40415
40586
|
userArgs: given.value,
|
|
@@ -40417,7 +40588,7 @@ class GodotServer {
|
|
|
40417
40588
|
silent
|
|
40418
40589
|
});
|
|
40419
40590
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
40420
|
-
const started = await this.launchKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined, headless || args["visible"] === true ? undefined : HIDDEN_DESKTOP, profiled);
|
|
40591
|
+
const started = await this.launchKeptGame(engine.value, cmdArgs, project.value.path, needsItsOwnProcess(asked.value) ? environmentFor({ ...asked.value, runtimeDirectory: runtimeDirectory() }) : undefined, headless || args["visible"] === true ? undefined : HIDDEN_DESKTOP, profiled, exported === null);
|
|
40421
40592
|
if ("error" in started) {
|
|
40422
40593
|
return this.createErrorResponse(`The game could not be started: ${started.error}`, [
|
|
40423
40594
|
"editor_status says which Godot this server runs and whether it answers"
|
|
@@ -40426,7 +40597,7 @@ class GodotServer {
|
|
|
40426
40597
|
started.announcedBefore = alreadyPlaying;
|
|
40427
40598
|
started.bootScene = runtimeWaitMs.scene;
|
|
40428
40599
|
this.activeProcess = started;
|
|
40429
|
-
const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ? ` The editor cannot be handed arguments for a game it plays, so this one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? ` The editor plays a game with its own environment, so a run given one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}, and silent: false plays it through the editor.${OWN_DEBUGGER}` : "";
|
|
40600
|
+
const spawnedInstead = exported !== null ? " An exported build is started without a debugger, since a release build quits when given one, so breakpoints and debug_state do not reach it." : this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ? ` The editor cannot be handed arguments for a game it plays, so this one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? ` The editor plays a game with its own environment, so a run given one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}, and silent: false plays it through the editor.${OWN_DEBUGGER}` : "";
|
|
40430
40601
|
const endedForThis = endedToStartThis(ended);
|
|
40431
40602
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
40432
40603
|
const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
|
|
@@ -40436,6 +40607,7 @@ class GodotServer {
|
|
|
40436
40607
|
through: "gdharness",
|
|
40437
40608
|
alsoRunning: stranded.length === 0 ? undefined : stranded.map((one) => one.pid),
|
|
40438
40609
|
pid: started.pid,
|
|
40610
|
+
executable: exported?.ok === true ? exported.relativePath : undefined,
|
|
40439
40611
|
arguments: cmdArgs,
|
|
40440
40612
|
argsNote: engineFlagsNote(given.value),
|
|
40441
40613
|
transcript: started.transcript,
|
|
@@ -40447,7 +40619,7 @@ class GodotServer {
|
|
|
40447
40619
|
endedPreviousRun: endedPreviousRun(ended),
|
|
40448
40620
|
previousRunLeft: previousRunLeft(ended),
|
|
40449
40621
|
...previousRunHow(ended),
|
|
40450
|
-
runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
40622
|
+
runtime: exported !== null ? undefined : await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
|
|
40451
40623
|
message: `Use editor_output for what it prints and editor_run stop to end it.${started.keeper !== undefined && handed.length > 0 ? " It stops at the lines under breakpoints, and debug_state stack reads where it is held." : ""}${spawnedInstead}${endedForThis}${alsoRunning}`
|
|
40452
40624
|
});
|
|
40453
40625
|
}
|
|
@@ -40538,7 +40710,7 @@ class GodotServer {
|
|
|
40538
40710
|
}
|
|
40539
40711
|
let announcedAt;
|
|
40540
40712
|
try {
|
|
40541
|
-
announcedAt =
|
|
40713
|
+
announcedAt = statSync10(endpoint.file).mtimeMs;
|
|
40542
40714
|
} catch {
|
|
40543
40715
|
return;
|
|
40544
40716
|
}
|
|
@@ -40887,7 +41059,10 @@ class GodotServer {
|
|
|
40887
41059
|
return gone ? "gone" : "lingering";
|
|
40888
41060
|
}
|
|
40889
41061
|
spawnGame(godotPath, cmdArgs, env) {
|
|
40890
|
-
const child =
|
|
41062
|
+
const child = spawnHidden(godotPath, cmdArgs, {
|
|
41063
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
41064
|
+
...env ? { env } : {}
|
|
41065
|
+
});
|
|
40891
41066
|
const cancelled = callSignal();
|
|
40892
41067
|
if (cancelled !== undefined) {
|
|
40893
41068
|
const end = () => {
|
|
@@ -40960,7 +41135,7 @@ class GodotServer {
|
|
|
40960
41135
|
});
|
|
40961
41136
|
return Object.assign(started, { settled });
|
|
40962
41137
|
}
|
|
40963
|
-
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false) {
|
|
41138
|
+
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false, debugged = true) {
|
|
40964
41139
|
const startedAt = Date.now();
|
|
40965
41140
|
const transcript = openTranscript(startedAt);
|
|
40966
41141
|
closeSync2(transcript.fd);
|
|
@@ -40976,7 +41151,7 @@ class GodotServer {
|
|
|
40976
41151
|
projectPath,
|
|
40977
41152
|
...this.servedBy(),
|
|
40978
41153
|
...profile === undefined ? {} : { profile },
|
|
40979
|
-
breakpoints: this.breakpointsForAGame(projectPath)
|
|
41154
|
+
...debugged ? { breakpoints: this.breakpointsForAGame(projectPath) } : { debugged: false }
|
|
40980
41155
|
},
|
|
40981
41156
|
...desktop === undefined ? {} : { desktop }
|
|
40982
41157
|
});
|
|
@@ -41940,8 +42115,8 @@ class GodotServer {
|
|
|
41940
42115
|
try {
|
|
41941
42116
|
const status = asParams(await this.godotBridge.invokeTool("scan_status", {}));
|
|
41942
42117
|
return status["scanning"] === true || status["importing"] === true;
|
|
41943
|
-
} catch {
|
|
41944
|
-
return
|
|
42118
|
+
} catch (error) {
|
|
42119
|
+
return error instanceof BridgeTimeout;
|
|
41945
42120
|
}
|
|
41946
42121
|
}
|
|
41947
42122
|
async handleRescanFilesystem(args) {
|
|
@@ -41952,7 +42127,7 @@ class GodotServer {
|
|
|
41952
42127
|
let writtenBefore = null;
|
|
41953
42128
|
let blind = { unseen: [] };
|
|
41954
42129
|
let waitedForEditorMs = 0;
|
|
41955
|
-
let
|
|
42130
|
+
let startUnanswered = false;
|
|
41956
42131
|
let completedBefore;
|
|
41957
42132
|
let uidsReread = [];
|
|
41958
42133
|
let uidsDuplicatedOnDisk = [];
|
|
@@ -41969,7 +42144,11 @@ class GodotServer {
|
|
|
41969
42144
|
before = projectPath === "" ? null : cachedClasses(projectPath);
|
|
41970
42145
|
writtenBefore = projectPath === "" ? null : cacheWrittenAt(projectPath);
|
|
41971
42146
|
blind = await this.classesTheEditorCannotSee(args);
|
|
41972
|
-
first = await this.
|
|
42147
|
+
const first = await this.askViaBridge("rescan_filesystem", args);
|
|
42148
|
+
if (first instanceof BridgeTimeout) {
|
|
42149
|
+
startUnanswered = true;
|
|
42150
|
+
break;
|
|
42151
|
+
}
|
|
41973
42152
|
if (first.isError) {
|
|
41974
42153
|
return first;
|
|
41975
42154
|
}
|
|
@@ -41992,7 +42171,17 @@ class GodotServer {
|
|
|
41992
42171
|
let idleSince = null;
|
|
41993
42172
|
while (Date.now() - started < timeoutMs) {
|
|
41994
42173
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
41995
|
-
|
|
42174
|
+
let status;
|
|
42175
|
+
try {
|
|
42176
|
+
status = asParams(await this.godotBridge.invokeTool("rescan_filesystem", { ...args, statusOnly: true }));
|
|
42177
|
+
} catch (error) {
|
|
42178
|
+
if (!(error instanceof BridgeTimeout)) {
|
|
42179
|
+
throw error;
|
|
42180
|
+
}
|
|
42181
|
+
busy = true;
|
|
42182
|
+
idleSince = null;
|
|
42183
|
+
continue;
|
|
42184
|
+
}
|
|
41996
42185
|
const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
|
|
41997
42186
|
const completed = readNumber(status, "scansCompleted");
|
|
41998
42187
|
const notYetOver = completedBefore !== undefined && completed !== undefined && completed <= completedBefore;
|
|
@@ -42075,12 +42264,20 @@ class GodotServer {
|
|
|
42075
42264
|
}
|
|
42076
42265
|
const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
|
|
42077
42266
|
const unseen = checked.unseen;
|
|
42078
|
-
const
|
|
42267
|
+
const carried = projectPath === "" ? [] : this.unseenBeforeAnUnfinishedScan(projectPath);
|
|
42268
|
+
const unseenBefore = [...new Set([...blind.unseen.map((one) => one.className), ...carried])];
|
|
42269
|
+
const broughtIn = busy ? [] : unseenBefore.filter((name) => !unseen.some((still) => still.className === name));
|
|
42270
|
+
if (projectPath !== "") {
|
|
42271
|
+
this.noteUnseenBeforeAnUnfinishedScan(projectPath, busy ? unseenBefore : []);
|
|
42272
|
+
}
|
|
42079
42273
|
const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
|
|
42080
42274
|
const stillGone = lost.filter((name) => !restored.includes(name));
|
|
42081
42275
|
const notes = [];
|
|
42082
42276
|
if (busy) {
|
|
42083
|
-
notes.push("The editor was still scanning or importing when the wait ran out, so new files may not be visible yet.");
|
|
42277
|
+
notes.push(startUnanswered ? "The editor took the rescan in and did not answer it before the wait ran out, which is an editor importing: a hidden editor imports on the thread that answers, and answers nothing until it is done. It carries the scan out once it is free, so new files may not be visible yet." : "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet.");
|
|
42278
|
+
if (unseenBefore.length > 0) {
|
|
42279
|
+
notes.push(`Scripts naming ${unseenBefore.join(", ")}, which the editor could not see before this scan, were compiled without them and stay that way until reloaded. editor_rescan again once the editor is idle reloads them, for any server on this project, until this editor is restarted.`);
|
|
42280
|
+
}
|
|
42084
42281
|
} else if (stillGone.length > 0) {
|
|
42085
42282
|
notes.push("The scan wrote the class cache from the list this editor is holding, that list is shorter than the file was, and rebuilding the cache from the files did not bring these back. Every engine that reads the cache next, including a test run, will report them as unknown identifiers. Restart the editor with editor_launch restart before scanning again.");
|
|
42086
42283
|
} else if (lost.length > 0) {
|
|
@@ -42198,8 +42395,9 @@ class GodotServer {
|
|
|
42198
42395
|
const droppedByTheEditor = lost.filter((name) => !cameBack.includes(name));
|
|
42199
42396
|
const seenLosing = droppedByTheEditor.length > 0 && this.godotBridge.isConnected();
|
|
42200
42397
|
writeClassNote(projectPath, {
|
|
42398
|
+
...noted,
|
|
42201
42399
|
rebuiltWith: [...after.keys()].sort(),
|
|
42202
|
-
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } :
|
|
42400
|
+
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : {}
|
|
42203
42401
|
});
|
|
42204
42402
|
const notes = [];
|
|
42205
42403
|
if (seenLosing) {
|
|
@@ -42273,6 +42471,10 @@ class GodotServer {
|
|
|
42273
42471
|
if (moveTo !== null && !moveTo.ok) {
|
|
42274
42472
|
return this.createErrorResponse(moveTo.reason);
|
|
42275
42473
|
}
|
|
42474
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
42475
|
+
if (waited !== null) {
|
|
42476
|
+
return waited;
|
|
42477
|
+
}
|
|
42276
42478
|
const languageServer = {
|
|
42277
42479
|
problem: null,
|
|
42278
42480
|
initialized: false
|
|
@@ -42515,6 +42717,16 @@ class GodotServer {
|
|
|
42515
42717
|
]);
|
|
42516
42718
|
}
|
|
42517
42719
|
async handleViaBridge(toolName, args, askedAs) {
|
|
42720
|
+
const answered = await this.askViaBridge(toolName, args, askedAs);
|
|
42721
|
+
if (!(answered instanceof BridgeTimeout)) {
|
|
42722
|
+
return answered;
|
|
42723
|
+
}
|
|
42724
|
+
return this.createErrorResponse(`The editor took ${toolName} in and did not answer within ${String(answered.waitedMs / 1000)}s. An editor importing files answers nothing until the import is over, and a hidden one imports on the thread that answers.`, [
|
|
42725
|
+
"editor_status says whether it is scanning or importing",
|
|
42726
|
+
"The editor still carries the request out once it is free, so read what it changed before asking again"
|
|
42727
|
+
]);
|
|
42728
|
+
}
|
|
42729
|
+
async askViaBridge(toolName, args, askedAs) {
|
|
42518
42730
|
if (!this.godotBridge.isConnected()) {
|
|
42519
42731
|
const alternative = askedAs !== undefined && Object.hasOwn(EDITOR_FREE_INSTEAD, askedAs) ? EDITOR_FREE_INSTEAD[askedAs] : undefined;
|
|
42520
42732
|
const refused = await this.noEditorYet(args, alternative === undefined ? [] : [alternative]);
|
|
@@ -42533,6 +42745,9 @@ class GodotServer {
|
|
|
42533
42745
|
try {
|
|
42534
42746
|
return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()));
|
|
42535
42747
|
} catch (error) {
|
|
42748
|
+
if (error instanceof BridgeTimeout) {
|
|
42749
|
+
return error;
|
|
42750
|
+
}
|
|
42536
42751
|
const message = errorMessage(error);
|
|
42537
42752
|
if (message.includes(`Unknown tool: ${toolName}`)) {
|
|
42538
42753
|
const status = this.godotBridge.getStatus();
|
|
@@ -42602,7 +42817,7 @@ class GodotServer {
|
|
|
42602
42817
|
return !run.announcedBefore.has(one.pid);
|
|
42603
42818
|
}
|
|
42604
42819
|
try {
|
|
42605
|
-
return
|
|
42820
|
+
return statSync10(one.file).mtimeMs >= run.startedAt;
|
|
42606
42821
|
} catch {
|
|
42607
42822
|
return false;
|
|
42608
42823
|
}
|