gdharness 1.2.27 → 1.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +804 -464
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +378 -8
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -7
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +33 -7
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -3
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +89 -15
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +25 -3
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +120 -0
- package/build/index.js +669 -342
- package/build/keeper.js +20 -17
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -3356,27 +3356,25 @@ 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,
|
|
3363
3362
|
closeSync as closeSync2,
|
|
3364
3363
|
constants,
|
|
3365
3364
|
copyFileSync,
|
|
3366
|
-
existsSync as
|
|
3365
|
+
existsSync as existsSync18,
|
|
3367
3366
|
fstatSync,
|
|
3368
3367
|
mkdirSync as mkdirSync14,
|
|
3369
3368
|
openSync as openSync3,
|
|
3370
|
-
readdirSync as
|
|
3371
|
-
readFileSync as
|
|
3369
|
+
readdirSync as readdirSync10,
|
|
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
|
-
import { basename as
|
|
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)}`;
|
|
@@ -29517,33 +29623,114 @@ class GodotLocator {
|
|
|
29517
29623
|
}
|
|
29518
29624
|
|
|
29519
29625
|
// src/headless.ts
|
|
29520
|
-
import { existsSync as
|
|
29521
|
-
import { dirname as
|
|
29626
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14, realpathSync as realpathSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
29627
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
29628
|
+
|
|
29629
|
+
// src/library-copies.ts
|
|
29630
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync12, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
29631
|
+
import { basename as basename2, dirname as dirname8, isAbsolute as isAbsolute3, join as join12 } from "node:path";
|
|
29632
|
+
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
29633
|
+
const setAside = [];
|
|
29634
|
+
const swept = [];
|
|
29635
|
+
if (platform !== "win32") {
|
|
29636
|
+
return { setAside, swept };
|
|
29637
|
+
}
|
|
29638
|
+
for (const library of windowsLibraries(projectPath)) {
|
|
29639
|
+
const folder = dirname8(library);
|
|
29640
|
+
const copy = `~${basename2(library)}`;
|
|
29641
|
+
if (existsSync9(join12(folder, copy))) {
|
|
29642
|
+
const aside = join12(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
29643
|
+
try {
|
|
29644
|
+
renameSync2(join12(folder, copy), aside);
|
|
29645
|
+
setAside.push(aside);
|
|
29646
|
+
} catch {}
|
|
29647
|
+
}
|
|
29648
|
+
let names;
|
|
29649
|
+
try {
|
|
29650
|
+
names = readdirSync4(folder);
|
|
29651
|
+
} catch {
|
|
29652
|
+
continue;
|
|
29653
|
+
}
|
|
29654
|
+
for (const name of names) {
|
|
29655
|
+
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
29656
|
+
try {
|
|
29657
|
+
unlinkSync2(join12(folder, name));
|
|
29658
|
+
swept.push(join12(folder, name));
|
|
29659
|
+
} catch {}
|
|
29660
|
+
}
|
|
29661
|
+
}
|
|
29662
|
+
}
|
|
29663
|
+
return { setAside, swept };
|
|
29664
|
+
}
|
|
29665
|
+
function windowsLibraries(projectPath) {
|
|
29666
|
+
const listed = readOrNull(join12(projectPath, ".godot", "extension_list.cfg"));
|
|
29667
|
+
if (listed === null) {
|
|
29668
|
+
return [];
|
|
29669
|
+
}
|
|
29670
|
+
const libraries = new Set;
|
|
29671
|
+
for (const line of listed.split(/\r?\n/)) {
|
|
29672
|
+
const manifest = resolveResource(projectPath, projectPath, line.trim());
|
|
29673
|
+
const text = manifest === null ? null : readOrNull(manifest);
|
|
29674
|
+
if (manifest === null || text === null) {
|
|
29675
|
+
continue;
|
|
29676
|
+
}
|
|
29677
|
+
let inLibraries = false;
|
|
29678
|
+
for (const row of text.split(/\r?\n/)) {
|
|
29679
|
+
const section = /^\s*\[([^\]]+)\]/.exec(row)?.[1];
|
|
29680
|
+
if (section !== undefined) {
|
|
29681
|
+
inLibraries = section.trim() === "libraries";
|
|
29682
|
+
continue;
|
|
29683
|
+
}
|
|
29684
|
+
const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
|
|
29685
|
+
const library = named === undefined ? null : resolveResource(projectPath, dirname8(manifest), named);
|
|
29686
|
+
if (library !== null) {
|
|
29687
|
+
libraries.add(library);
|
|
29688
|
+
}
|
|
29689
|
+
}
|
|
29690
|
+
}
|
|
29691
|
+
return [...libraries];
|
|
29692
|
+
}
|
|
29693
|
+
function resolveResource(projectPath, relativeTo, path) {
|
|
29694
|
+
if (path === "") {
|
|
29695
|
+
return null;
|
|
29696
|
+
}
|
|
29697
|
+
if (path.startsWith("res://")) {
|
|
29698
|
+
return join12(projectPath, path.slice("res://".length));
|
|
29699
|
+
}
|
|
29700
|
+
return isAbsolute3(path) ? path : join12(relativeTo, path);
|
|
29701
|
+
}
|
|
29702
|
+
function readOrNull(path) {
|
|
29703
|
+
try {
|
|
29704
|
+
return readFileSync12(path, "utf8");
|
|
29705
|
+
} catch {
|
|
29706
|
+
return null;
|
|
29707
|
+
}
|
|
29708
|
+
}
|
|
29522
29709
|
|
|
29523
29710
|
// src/reimport.ts
|
|
29524
29711
|
import { createHash as createHash2 } from "node:crypto";
|
|
29525
|
-
import { existsSync as
|
|
29526
|
-
import { basename as
|
|
29712
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, rmSync as rmSync6, statSync as statSync6, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
29713
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
29527
29714
|
function forceNextImport(projectPath, resourcePath) {
|
|
29528
29715
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
29529
|
-
const sidecar =
|
|
29530
|
-
if (!
|
|
29716
|
+
const sidecar = join13(projectPath, `${relative}.import`);
|
|
29717
|
+
if (!existsSync10(sidecar)) {
|
|
29531
29718
|
return;
|
|
29532
29719
|
}
|
|
29533
|
-
const { atime, mtimeMs } =
|
|
29534
|
-
writeFileSync8(sidecar,
|
|
29720
|
+
const { atime, mtimeMs } = statSync6(sidecar);
|
|
29721
|
+
writeFileSync8(sidecar, readFileSync13(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
29535
29722
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
29536
|
-
|
|
29723
|
+
rmSync6(importRecord(projectPath, resourcePath), { force: true });
|
|
29537
29724
|
}
|
|
29538
29725
|
function importRecord(projectPath, resourcePath) {
|
|
29539
29726
|
const digest = createHash2("md5").update(resourcePath).digest("hex");
|
|
29540
|
-
return
|
|
29727
|
+
return join13(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
|
|
29541
29728
|
}
|
|
29542
29729
|
|
|
29543
29730
|
// src/scratch.ts
|
|
29544
|
-
import { existsSync as
|
|
29731
|
+
import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync7 } from "node:fs";
|
|
29545
29732
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
29546
|
-
import { join as
|
|
29733
|
+
import { join as join14 } from "node:path";
|
|
29547
29734
|
import process7 from "node:process";
|
|
29548
29735
|
var SWEPT = { recursive: true, force: true };
|
|
29549
29736
|
var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
@@ -29558,12 +29745,12 @@ var KINDS = [
|
|
|
29558
29745
|
"tests"
|
|
29559
29746
|
];
|
|
29560
29747
|
function scratchDirectory(kind) {
|
|
29561
|
-
return mkdtempSync(
|
|
29748
|
+
return mkdtempSync(join14(tmpdir4(), `gdharness-${kind}-${process7.pid}-`));
|
|
29562
29749
|
}
|
|
29563
29750
|
function sweepAbandonedScratch(directory = tmpdir4()) {
|
|
29564
29751
|
let entries;
|
|
29565
29752
|
try {
|
|
29566
|
-
entries =
|
|
29753
|
+
entries = readdirSync5(directory);
|
|
29567
29754
|
} catch {
|
|
29568
29755
|
return;
|
|
29569
29756
|
}
|
|
@@ -29574,7 +29761,7 @@ function sweepAbandonedScratch(directory = tmpdir4()) {
|
|
|
29574
29761
|
continue;
|
|
29575
29762
|
}
|
|
29576
29763
|
try {
|
|
29577
|
-
remove(
|
|
29764
|
+
remove(join14(directory, entry));
|
|
29578
29765
|
} catch {}
|
|
29579
29766
|
}
|
|
29580
29767
|
}
|
|
@@ -29600,7 +29787,7 @@ function discard(...paths) {
|
|
|
29600
29787
|
return left;
|
|
29601
29788
|
}
|
|
29602
29789
|
function remove(path) {
|
|
29603
|
-
|
|
29790
|
+
rmSync7(path, SWEPT);
|
|
29604
29791
|
}
|
|
29605
29792
|
function tryAgain(path, attempt) {
|
|
29606
29793
|
if (attempt === 0) {
|
|
@@ -29612,7 +29799,7 @@ function tryAgain(path, attempt) {
|
|
|
29612
29799
|
lastTryOnExit();
|
|
29613
29800
|
}
|
|
29614
29801
|
const after = AGAIN_AFTER_MS[attempt];
|
|
29615
|
-
if (after === undefined || !
|
|
29802
|
+
if (after === undefined || !existsSync11(path)) {
|
|
29616
29803
|
waiting.delete(path);
|
|
29617
29804
|
if (after === undefined) {
|
|
29618
29805
|
atExit.add(path);
|
|
@@ -29703,7 +29890,7 @@ function refusalsIn(log) {
|
|
|
29703
29890
|
}
|
|
29704
29891
|
function scriptFault(problems, script) {
|
|
29705
29892
|
const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
|
|
29706
|
-
const directories = [...new Set([spelled(
|
|
29893
|
+
const directories = [...new Set([spelled(dirname9(script)), spelled(resolvedOr(dirname9(script)))])];
|
|
29707
29894
|
return problems.find((entry) => {
|
|
29708
29895
|
const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
|
|
29709
29896
|
return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
|
|
@@ -29728,7 +29915,7 @@ function librariesNotCopied(messages) {
|
|
|
29728
29915
|
}
|
|
29729
29916
|
function extensionRetryNote(libraries, stillNotLoaded = []) {
|
|
29730
29917
|
const second = stillNotLoaded.length === 0 ? "so the import was run again and loaded the extension, and the messages are from that second run." : `so the import was run again, and it still could not load ${stillNotLoaded.join(" and ")}: this answer is from an import without ${stillNotLoaded.length === 1 ? "it" : "them"}, and its messages say what the engine hit.`;
|
|
29731
|
-
return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "
|
|
29918
|
+
return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "the next import, export or editor this server starts on the project deletes once the editor has exited.";
|
|
29732
29919
|
}
|
|
29733
29920
|
async function runImport(godotPath, projectPath, options = {}) {
|
|
29734
29921
|
const once = options.once ?? importOnce;
|
|
@@ -29736,6 +29923,7 @@ async function runImport(godotPath, projectPath, options = {}) {
|
|
|
29736
29923
|
for (const target of options.forcing ?? []) {
|
|
29737
29924
|
forceNextImport(projectPath, target);
|
|
29738
29925
|
}
|
|
29926
|
+
freeLibraryCopies(projectPath);
|
|
29739
29927
|
return once(godotPath, projectPath);
|
|
29740
29928
|
};
|
|
29741
29929
|
const first = await pass();
|
|
@@ -29759,7 +29947,7 @@ async function importOnce(godotPath, projectPath) {
|
|
|
29759
29947
|
ran = await runEngine(godotPath, [
|
|
29760
29948
|
"--headless",
|
|
29761
29949
|
"--log-file",
|
|
29762
|
-
|
|
29950
|
+
join15(logDir, "engine.log"),
|
|
29763
29951
|
"--path",
|
|
29764
29952
|
projectPath,
|
|
29765
29953
|
"--import"
|
|
@@ -29779,13 +29967,13 @@ async function importOnce(godotPath, projectPath) {
|
|
|
29779
29967
|
}
|
|
29780
29968
|
async function runOperation(engine, operation, params, projectPath) {
|
|
29781
29969
|
const paramsDir = scratchDirectory("params");
|
|
29782
|
-
const paramsFile =
|
|
29783
|
-
const answerFile =
|
|
29970
|
+
const paramsFile = join15(paramsDir, `${operation}.json`);
|
|
29971
|
+
const answerFile = join15(paramsDir, "answer.json");
|
|
29784
29972
|
writeFileSync9(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
29785
29973
|
const args = [
|
|
29786
29974
|
"--headless",
|
|
29787
29975
|
"--log-file",
|
|
29788
|
-
|
|
29976
|
+
join15(paramsDir, "engine.log"),
|
|
29789
29977
|
"--path",
|
|
29790
29978
|
projectPath,
|
|
29791
29979
|
"--script",
|
|
@@ -29855,10 +30043,10 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
29855
30043
|
};
|
|
29856
30044
|
}
|
|
29857
30045
|
function answerIn(file) {
|
|
29858
|
-
if (!
|
|
30046
|
+
if (!existsSync12(file)) {
|
|
29859
30047
|
return { kind: "none" };
|
|
29860
30048
|
}
|
|
29861
|
-
const text =
|
|
30049
|
+
const text = readFileSync14(file, "utf8");
|
|
29862
30050
|
try {
|
|
29863
30051
|
const parsed = JSON.parse(text);
|
|
29864
30052
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
|
|
@@ -30431,20 +30619,20 @@ function askTheKeeper(address, line, withinMs) {
|
|
|
30431
30619
|
}
|
|
30432
30620
|
|
|
30433
30621
|
// src/lifecycle-log.ts
|
|
30434
|
-
import { appendFileSync, mkdirSync as mkdirSync8, renameSync, statSync as
|
|
30435
|
-
import { dirname as
|
|
30622
|
+
import { appendFileSync, mkdirSync as mkdirSync8, renameSync as renameSync3, statSync as statSync7 } from "node:fs";
|
|
30623
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
30436
30624
|
import process8 from "node:process";
|
|
30437
30625
|
function lifecycleLogPath() {
|
|
30438
|
-
return
|
|
30626
|
+
return join16(runtimeDirectory(), "servers.log");
|
|
30439
30627
|
}
|
|
30440
30628
|
var LIFECYCLE_LOG_LIMIT = 1e6;
|
|
30441
30629
|
function writeLifecycle(event, fields = {}) {
|
|
30442
30630
|
const path = lifecycleLogPath();
|
|
30443
30631
|
try {
|
|
30444
|
-
mkdirSync8(
|
|
30445
|
-
if ((
|
|
30632
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
30633
|
+
if ((statSync7(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
30446
30634
|
try {
|
|
30447
|
-
|
|
30635
|
+
renameSync3(path, `${path}.1`);
|
|
30448
30636
|
} catch {}
|
|
30449
30637
|
}
|
|
30450
30638
|
const line = {
|
|
@@ -30474,10 +30662,10 @@ function callsInFlight(calls, now = Date.now()) {
|
|
|
30474
30662
|
}
|
|
30475
30663
|
|
|
30476
30664
|
// src/lsp_client.ts
|
|
30477
|
-
import { existsSync as
|
|
30665
|
+
import { existsSync as existsSync13, realpathSync as realpathSync3 } from "node:fs";
|
|
30478
30666
|
import { readFile, realpath } from "node:fs/promises";
|
|
30479
30667
|
import { createConnection as createConnection3 } from "node:net";
|
|
30480
|
-
import { dirname as
|
|
30668
|
+
import { dirname as dirname11, join as join17, resolve as resolve3 } from "node:path";
|
|
30481
30669
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
30482
30670
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
30483
30671
|
var DEFAULT_LSP_PORT = 6005;
|
|
@@ -30675,7 +30863,7 @@ class GodotLSPClient {
|
|
|
30675
30863
|
if (this.initialized) {
|
|
30676
30864
|
return;
|
|
30677
30865
|
}
|
|
30678
|
-
const rootPath = this.rootPath ??
|
|
30866
|
+
const rootPath = this.rootPath ?? dirname11(resolve3(filePath));
|
|
30679
30867
|
await this.initialize(rootPath);
|
|
30680
30868
|
}
|
|
30681
30869
|
async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
|
|
@@ -31233,7 +31421,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
31233
31421
|
} catch {
|
|
31234
31422
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
31235
31423
|
}
|
|
31236
|
-
if (!
|
|
31424
|
+
if (!existsSync13(join17(projectPath, "project.godot"))) {
|
|
31237
31425
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
31238
31426
|
}
|
|
31239
31427
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -31303,7 +31491,6 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
31303
31491
|
}
|
|
31304
31492
|
|
|
31305
31493
|
// src/outside.ts
|
|
31306
|
-
import { spawn as spawn3 } from "node:child_process";
|
|
31307
31494
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
31308
31495
|
function changesFrom(env, base) {
|
|
31309
31496
|
const changes = {};
|
|
@@ -31337,9 +31524,8 @@ function readLaunched(printed) {
|
|
|
31337
31524
|
}
|
|
31338
31525
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
31339
31526
|
return await new Promise((resolve) => {
|
|
31340
|
-
const launcher =
|
|
31341
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31342
|
-
windowsHide: true
|
|
31527
|
+
const launcher = spawnHidden(runtime, [keeper, "launch"], {
|
|
31528
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
31343
31529
|
});
|
|
31344
31530
|
sendSpec(launcher, {
|
|
31345
31531
|
command: spec.command,
|
|
@@ -31403,9 +31589,6 @@ function notListeningNote(servers, greetedAgoMs) {
|
|
|
31403
31589
|
}
|
|
31404
31590
|
|
|
31405
31591
|
// src/process-time.ts
|
|
31406
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
31407
|
-
import { promisify as promisify3 } from "node:util";
|
|
31408
|
-
var run3 = promisify3(execFile3);
|
|
31409
31592
|
var ASK_TIMEOUT_MS2 = 6000;
|
|
31410
31593
|
async function cpuSecondsOf(pid) {
|
|
31411
31594
|
try {
|
|
@@ -31420,7 +31603,7 @@ async function secondsOnWindows(pid) {
|
|
|
31420
31603
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
31421
31604
|
}
|
|
31422
31605
|
async function askPosix2(pid) {
|
|
31423
|
-
const { stdout } = await
|
|
31606
|
+
const { stdout } = await runHidden("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
|
|
31424
31607
|
return secondsFromClock(stdout.trim());
|
|
31425
31608
|
}
|
|
31426
31609
|
function secondsFromClock(printed) {
|
|
@@ -31440,8 +31623,8 @@ function secondsFromClock(printed) {
|
|
|
31440
31623
|
}
|
|
31441
31624
|
|
|
31442
31625
|
// src/project-scan.ts
|
|
31443
|
-
import { existsSync as
|
|
31444
|
-
import { extname, join as
|
|
31626
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "node:fs";
|
|
31627
|
+
import { extname, join as join18 } from "node:path";
|
|
31445
31628
|
var SKIPPED = new Set(["node_modules"]);
|
|
31446
31629
|
function skipped(name) {
|
|
31447
31630
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
@@ -31451,10 +31634,10 @@ function steppedOver(directory) {
|
|
|
31451
31634
|
return gdignored(directory) || installedByUs(directory);
|
|
31452
31635
|
}
|
|
31453
31636
|
function gdignored(directory) {
|
|
31454
|
-
return
|
|
31637
|
+
return existsSync14(join18(directory, ".gdignore"));
|
|
31455
31638
|
}
|
|
31456
31639
|
function installedByUs(directory) {
|
|
31457
|
-
return
|
|
31640
|
+
return existsSync14(join18(directory, VERSION_MARKER));
|
|
31458
31641
|
}
|
|
31459
31642
|
var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
31460
31643
|
var ASSET_EXTENSIONS = new Set([
|
|
@@ -31493,12 +31676,12 @@ function projectStructure(projectPath) {
|
|
|
31493
31676
|
if (steppedOver(directory)) {
|
|
31494
31677
|
return;
|
|
31495
31678
|
}
|
|
31496
|
-
for (const entry of
|
|
31679
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31497
31680
|
if (skipped(entry.name)) {
|
|
31498
31681
|
continue;
|
|
31499
31682
|
}
|
|
31500
31683
|
if (entry.isDirectory()) {
|
|
31501
|
-
visit(
|
|
31684
|
+
visit(join18(directory, entry.name));
|
|
31502
31685
|
} else if (entry.isFile()) {
|
|
31503
31686
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
31504
31687
|
if (SCENE_EXTENSIONS.has(extension)) {
|
|
@@ -31522,13 +31705,13 @@ function projectFiles(projectPath, extensions) {
|
|
|
31522
31705
|
if (steppedOver(directory)) {
|
|
31523
31706
|
return;
|
|
31524
31707
|
}
|
|
31525
|
-
for (const entry of
|
|
31708
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31526
31709
|
if (skipped(entry.name)) {
|
|
31527
31710
|
continue;
|
|
31528
31711
|
}
|
|
31529
31712
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
31530
31713
|
if (entry.isDirectory()) {
|
|
31531
|
-
visit(
|
|
31714
|
+
visit(join18(directory, entry.name), spelled);
|
|
31532
31715
|
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
31533
31716
|
found.push(spelled);
|
|
31534
31717
|
}
|
|
@@ -31544,20 +31727,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
31544
31727
|
if (steppedOver(directory)) {
|
|
31545
31728
|
return;
|
|
31546
31729
|
}
|
|
31547
|
-
for (const entry of
|
|
31730
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31548
31731
|
if (skipped(entry.name)) {
|
|
31549
31732
|
continue;
|
|
31550
31733
|
}
|
|
31551
31734
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
31552
31735
|
if (entry.isDirectory()) {
|
|
31553
|
-
visit(
|
|
31736
|
+
visit(join18(directory, entry.name), spelled);
|
|
31554
31737
|
continue;
|
|
31555
31738
|
}
|
|
31556
31739
|
if (!entry.isFile()) {
|
|
31557
31740
|
continue;
|
|
31558
31741
|
}
|
|
31559
31742
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
31560
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
31743
|
+
if (CARRIES_A_UID.has(extension) && !existsSync14(join18(directory, `${entry.name}.uid`))) {
|
|
31561
31744
|
found.push(spelled);
|
|
31562
31745
|
}
|
|
31563
31746
|
}
|
|
@@ -31585,11 +31768,11 @@ function projectTexts(projectPath) {
|
|
|
31585
31768
|
if (steppedOver(directory)) {
|
|
31586
31769
|
return;
|
|
31587
31770
|
}
|
|
31588
|
-
for (const entry of
|
|
31771
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31589
31772
|
if (skipped(entry.name)) {
|
|
31590
31773
|
continue;
|
|
31591
31774
|
}
|
|
31592
|
-
const absolute =
|
|
31775
|
+
const absolute = join18(directory, entry.name);
|
|
31593
31776
|
const spelled = `${prefix}${entry.name}`;
|
|
31594
31777
|
if (entry.isDirectory()) {
|
|
31595
31778
|
visit(absolute, `${spelled}/`);
|
|
@@ -31599,7 +31782,7 @@ function projectTexts(projectPath) {
|
|
|
31599
31782
|
if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
|
|
31600
31783
|
continue;
|
|
31601
31784
|
}
|
|
31602
|
-
const bytes =
|
|
31785
|
+
const bytes = readFileSync15(absolute);
|
|
31603
31786
|
if (!isBinary(bytes)) {
|
|
31604
31787
|
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
31605
31788
|
}
|
|
@@ -31615,11 +31798,11 @@ function ignoredTexts(projectPath) {
|
|
|
31615
31798
|
return;
|
|
31616
31799
|
}
|
|
31617
31800
|
const under = ignored || gdignored(directory);
|
|
31618
|
-
for (const entry of
|
|
31801
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31619
31802
|
if (skipped(entry.name)) {
|
|
31620
31803
|
continue;
|
|
31621
31804
|
}
|
|
31622
|
-
const absolute =
|
|
31805
|
+
const absolute = join18(directory, entry.name);
|
|
31623
31806
|
const spelled = `${prefix}${entry.name}`;
|
|
31624
31807
|
if (entry.isDirectory()) {
|
|
31625
31808
|
visit(absolute, `${spelled}/`, under);
|
|
@@ -31629,7 +31812,7 @@ function ignoredTexts(projectPath) {
|
|
|
31629
31812
|
if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
|
|
31630
31813
|
continue;
|
|
31631
31814
|
}
|
|
31632
|
-
const bytes =
|
|
31815
|
+
const bytes = readFileSync15(absolute);
|
|
31633
31816
|
if (!isBinary(bytes)) {
|
|
31634
31817
|
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
31635
31818
|
}
|
|
@@ -31652,14 +31835,14 @@ function searchProject(projectPath, options) {
|
|
|
31652
31835
|
if (steppedOver(directory)) {
|
|
31653
31836
|
return;
|
|
31654
31837
|
}
|
|
31655
|
-
for (const entry of
|
|
31838
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31656
31839
|
if (full()) {
|
|
31657
31840
|
return;
|
|
31658
31841
|
}
|
|
31659
31842
|
if (skipped(entry.name)) {
|
|
31660
31843
|
continue;
|
|
31661
31844
|
}
|
|
31662
|
-
const entryPath =
|
|
31845
|
+
const entryPath = join18(directory, entry.name);
|
|
31663
31846
|
if (entry.isDirectory()) {
|
|
31664
31847
|
visit(entryPath);
|
|
31665
31848
|
continue;
|
|
@@ -31668,7 +31851,7 @@ function searchProject(projectPath, options) {
|
|
|
31668
31851
|
if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
|
|
31669
31852
|
continue;
|
|
31670
31853
|
}
|
|
31671
|
-
const bytes =
|
|
31854
|
+
const bytes = readFileSync15(entryPath);
|
|
31672
31855
|
if (extensions === null && isBinary(bytes)) {
|
|
31673
31856
|
continue;
|
|
31674
31857
|
}
|
|
@@ -31830,8 +32013,8 @@ function setupPromptHandlers(server) {
|
|
|
31830
32013
|
}
|
|
31831
32014
|
|
|
31832
32015
|
// src/rename.ts
|
|
31833
|
-
import { existsSync as
|
|
31834
|
-
import { dirname as
|
|
32016
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
32017
|
+
import { dirname as dirname12, posix as posix2 } from "node:path";
|
|
31835
32018
|
class Script {
|
|
31836
32019
|
path;
|
|
31837
32020
|
text;
|
|
@@ -32553,26 +32736,26 @@ function rewritesOf(plan, texts, move) {
|
|
|
32553
32736
|
}
|
|
32554
32737
|
function applyRewrites(rewrites) {
|
|
32555
32738
|
for (const rewrite of rewrites) {
|
|
32556
|
-
const now =
|
|
32739
|
+
const now = readFileSync16(rewrite.absolute, "utf8");
|
|
32557
32740
|
if (now !== rewrite.before) {
|
|
32558
32741
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
32559
32742
|
}
|
|
32560
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
32743
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync15(rewrite.writeTo)) {
|
|
32561
32744
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
32562
32745
|
}
|
|
32563
32746
|
}
|
|
32564
32747
|
const done = [];
|
|
32565
32748
|
try {
|
|
32566
32749
|
for (const rewrite of rewrites) {
|
|
32567
|
-
mkdirSync9(
|
|
32750
|
+
mkdirSync9(dirname12(rewrite.writeTo), { recursive: true });
|
|
32568
32751
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
32569
32752
|
writeFileSync10(staging, rewrite.after, "utf8");
|
|
32570
|
-
|
|
32753
|
+
renameSync4(staging, rewrite.writeTo);
|
|
32571
32754
|
let movedUid = false;
|
|
32572
32755
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
32573
|
-
|
|
32574
|
-
if (
|
|
32575
|
-
|
|
32756
|
+
unlinkSync3(rewrite.absolute);
|
|
32757
|
+
if (existsSync15(`${rewrite.absolute}.uid`)) {
|
|
32758
|
+
renameSync4(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
32576
32759
|
movedUid = true;
|
|
32577
32760
|
}
|
|
32578
32761
|
}
|
|
@@ -32583,9 +32766,9 @@ function applyRewrites(rewrites) {
|
|
|
32583
32766
|
try {
|
|
32584
32767
|
writeFileSync10(rewrite.absolute, rewrite.before, "utf8");
|
|
32585
32768
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
32586
|
-
|
|
32769
|
+
unlinkSync3(rewrite.writeTo);
|
|
32587
32770
|
if (movedUid) {
|
|
32588
|
-
|
|
32771
|
+
renameSync4(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
|
|
32589
32772
|
}
|
|
32590
32773
|
}
|
|
32591
32774
|
} catch {}
|
|
@@ -32595,11 +32778,11 @@ function applyRewrites(rewrites) {
|
|
|
32595
32778
|
}
|
|
32596
32779
|
|
|
32597
32780
|
// src/rename-symbol.ts
|
|
32598
|
-
import { existsSync as
|
|
32599
|
-
import { basename as
|
|
32781
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readdirSync as readdirSync7, realpathSync as realpathSync5, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
32782
|
+
import { basename as basename4, dirname as dirname13, join as join19, relative as relative3, resolve as resolve5 } from "node:path";
|
|
32600
32783
|
|
|
32601
32784
|
// src/resources.ts
|
|
32602
|
-
import { readFileSync as
|
|
32785
|
+
import { readFileSync as readFileSync17, realpathSync as realpathSync4 } from "node:fs";
|
|
32603
32786
|
import { extname as extname2, resolve as resolve4 } from "node:path";
|
|
32604
32787
|
var STATIC_RESOURCES = [
|
|
32605
32788
|
{
|
|
@@ -32830,7 +33013,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
32830
33013
|
const parsedUri = parseGodotUri(uri);
|
|
32831
33014
|
if (parsedUri.kind === "project-info") {
|
|
32832
33015
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
32833
|
-
const rawProject =
|
|
33016
|
+
const rawProject = readFileSync17(projectFilePath, "utf-8");
|
|
32834
33017
|
const parsedProject = parseProjectGodot(rawProject);
|
|
32835
33018
|
return {
|
|
32836
33019
|
mimeType: "application/json",
|
|
@@ -32839,7 +33022,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
32839
33022
|
}
|
|
32840
33023
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
32841
33024
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
32842
|
-
const text =
|
|
33025
|
+
const text = readFileSync17(filePath, "utf-8");
|
|
32843
33026
|
return {
|
|
32844
33027
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
32845
33028
|
text
|
|
@@ -32904,13 +33087,13 @@ function onDiskSpelling(path) {
|
|
|
32904
33087
|
let at = resolve5(path);
|
|
32905
33088
|
for (;; ) {
|
|
32906
33089
|
try {
|
|
32907
|
-
return
|
|
33090
|
+
return join19(realpathSync5.native(at), ...missing.reverse());
|
|
32908
33091
|
} catch {
|
|
32909
|
-
const parent =
|
|
33092
|
+
const parent = dirname13(at);
|
|
32910
33093
|
if (parent === at) {
|
|
32911
33094
|
return resolve5(path);
|
|
32912
33095
|
}
|
|
32913
|
-
missing.push(
|
|
33096
|
+
missing.push(basename4(at));
|
|
32914
33097
|
at = parent;
|
|
32915
33098
|
}
|
|
32916
33099
|
}
|
|
@@ -32957,7 +33140,7 @@ async function renameSymbol(request, services) {
|
|
|
32957
33140
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
32958
33141
|
};
|
|
32959
33142
|
}
|
|
32960
|
-
if (
|
|
33143
|
+
if (existsSync16(request.newScriptPath)) {
|
|
32961
33144
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
32962
33145
|
}
|
|
32963
33146
|
}
|
|
@@ -33133,7 +33316,7 @@ async function renameSymbol(request, services) {
|
|
|
33133
33316
|
scripts,
|
|
33134
33317
|
from: declaringPath,
|
|
33135
33318
|
to: moveTo,
|
|
33136
|
-
exists: (path) =>
|
|
33319
|
+
exists: (path) => existsSync16(`${projectPath}/${path.slice("res://".length)}`)
|
|
33137
33320
|
}, plan);
|
|
33138
33321
|
}
|
|
33139
33322
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? null : declaringPath);
|
|
@@ -33334,15 +33517,15 @@ ${mention.kind}`;
|
|
|
33334
33517
|
var LEFT_ALONE_ANSWER_LINES = 300;
|
|
33335
33518
|
var detailWrites = 0;
|
|
33336
33519
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
33337
|
-
const directory =
|
|
33520
|
+
const directory = join19(projectPath, ".godot", "gdharness-renames");
|
|
33338
33521
|
mkdirSync10(directory, { recursive: true });
|
|
33339
33522
|
detailWrites += 1;
|
|
33340
|
-
const path =
|
|
33523
|
+
const path = join19(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
33341
33524
|
writeFileSync11(path, `${JSON.stringify(detail, null, 2)}
|
|
33342
33525
|
`, "utf8");
|
|
33343
|
-
const kept =
|
|
33526
|
+
const kept = readdirSync7(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
33344
33527
|
for (const old of kept.slice(10)) {
|
|
33345
|
-
|
|
33528
|
+
rmSync8(join19(directory, old), { force: true });
|
|
33346
33529
|
}
|
|
33347
33530
|
return path;
|
|
33348
33531
|
}
|
|
@@ -33365,15 +33548,15 @@ function summaryOf(edits, mentions, rewrites, left) {
|
|
|
33365
33548
|
}
|
|
33366
33549
|
|
|
33367
33550
|
// src/restart-note.ts
|
|
33368
|
-
import { mkdirSync as mkdirSync11, readFileSync as
|
|
33369
|
-
import { dirname as
|
|
33551
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
|
33552
|
+
import { dirname as dirname14, join as join20 } from "node:path";
|
|
33370
33553
|
function restartNotePath(projectPath) {
|
|
33371
|
-
return
|
|
33554
|
+
return join20(projectPath, ".godot", "gdharness-restart.json");
|
|
33372
33555
|
}
|
|
33373
33556
|
function noteRestartBegun(note) {
|
|
33374
33557
|
const path = restartNotePath(note.projectPath);
|
|
33375
33558
|
try {
|
|
33376
|
-
mkdirSync11(
|
|
33559
|
+
mkdirSync11(dirname14(path), { recursive: true });
|
|
33377
33560
|
writeFileSync12(path, `${JSON.stringify(note, null, 2)}
|
|
33378
33561
|
`, "utf8");
|
|
33379
33562
|
} catch (error) {
|
|
@@ -33382,7 +33565,7 @@ function noteRestartBegun(note) {
|
|
|
33382
33565
|
}
|
|
33383
33566
|
function restartOwed(projectPath) {
|
|
33384
33567
|
try {
|
|
33385
|
-
const parsed = JSON.parse(
|
|
33568
|
+
const parsed = JSON.parse(readFileSync18(restartNotePath(projectPath), "utf8"));
|
|
33386
33569
|
if (typeof parsed !== "object" || parsed === null) {
|
|
33387
33570
|
return null;
|
|
33388
33571
|
}
|
|
@@ -33397,66 +33580,31 @@ function restartOwed(projectPath) {
|
|
|
33397
33580
|
}
|
|
33398
33581
|
function restartSettled(projectPath) {
|
|
33399
33582
|
try {
|
|
33400
|
-
|
|
33583
|
+
rmSync9(restartNotePath(projectPath), { force: true });
|
|
33401
33584
|
} catch (error) {
|
|
33402
33585
|
console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
|
|
33403
33586
|
}
|
|
33404
33587
|
}
|
|
33405
33588
|
|
|
33406
33589
|
// src/run-record.ts
|
|
33407
|
-
import { execFile as execFile4 } from "node:child_process";
|
|
33408
33590
|
import { createHash as createHash3 } from "node:crypto";
|
|
33409
|
-
import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as
|
|
33410
|
-
import { basename as
|
|
33411
|
-
import { promisify as promisify4 } from "node:util";
|
|
33412
|
-
|
|
33413
|
-
// src/whole-file.ts
|
|
33414
|
-
import { renameSync as renameSync3, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
33415
|
-
var HELD_FILE_MS = 1000;
|
|
33416
|
-
var HELD_FILE_RETRY_MS = 10;
|
|
33417
|
-
var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
33418
|
-
function writeWhole(path, text) {
|
|
33419
|
-
const partial = `${path}.${process.pid}.partial`;
|
|
33420
|
-
try {
|
|
33421
|
-
writeFileSync13(partial, text, "utf8");
|
|
33422
|
-
} catch {
|
|
33423
|
-
return false;
|
|
33424
|
-
}
|
|
33425
|
-
const until = Date.now() + HELD_FILE_MS;
|
|
33426
|
-
for (;; ) {
|
|
33427
|
-
try {
|
|
33428
|
-
renameSync3(partial, path);
|
|
33429
|
-
return true;
|
|
33430
|
-
} catch (error) {
|
|
33431
|
-
const code = error.code ?? "";
|
|
33432
|
-
if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
|
|
33433
|
-
try {
|
|
33434
|
-
rmSync9(partial, { force: true });
|
|
33435
|
-
} catch {}
|
|
33436
|
-
return false;
|
|
33437
|
-
}
|
|
33438
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
|
|
33439
|
-
}
|
|
33440
|
-
}
|
|
33441
|
-
}
|
|
33442
|
-
|
|
33443
|
-
// src/run-record.ts
|
|
33444
|
-
var execFileAsync = promisify4(execFile4);
|
|
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";
|
|
33592
|
+
import { basename as basename5, dirname as dirname15, join as join21, resolve as resolve6 } from "node:path";
|
|
33445
33593
|
function runsDirectory() {
|
|
33446
|
-
return
|
|
33594
|
+
return join21(runtimeDirectory(), "runs");
|
|
33447
33595
|
}
|
|
33448
33596
|
function realAsFarAsItExists(path) {
|
|
33449
33597
|
const missing = [];
|
|
33450
33598
|
let at = path;
|
|
33451
33599
|
for (;; ) {
|
|
33452
33600
|
try {
|
|
33453
|
-
return
|
|
33601
|
+
return join21(realpathSync6.native(at), ...missing);
|
|
33454
33602
|
} catch {
|
|
33455
|
-
const parent =
|
|
33603
|
+
const parent = dirname15(at);
|
|
33456
33604
|
if (parent === at) {
|
|
33457
33605
|
return path;
|
|
33458
33606
|
}
|
|
33459
|
-
missing.unshift(
|
|
33607
|
+
missing.unshift(basename5(at));
|
|
33460
33608
|
at = parent;
|
|
33461
33609
|
}
|
|
33462
33610
|
}
|
|
@@ -33467,10 +33615,10 @@ function projectKey(projectPath) {
|
|
|
33467
33615
|
return createHash3("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
33468
33616
|
}
|
|
33469
33617
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
33470
|
-
return
|
|
33618
|
+
return join21(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
33471
33619
|
}
|
|
33472
33620
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
33473
|
-
return
|
|
33621
|
+
return join21(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
33474
33622
|
}
|
|
33475
33623
|
function writeLatestRun(servedBy, latest) {
|
|
33476
33624
|
try {
|
|
@@ -33480,7 +33628,7 @@ function writeLatestRun(servedBy, latest) {
|
|
|
33480
33628
|
}
|
|
33481
33629
|
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
33482
33630
|
try {
|
|
33483
|
-
const fields = JSON.parse(
|
|
33631
|
+
const fields = JSON.parse(readFileSync19(latestRunPath(servedBy, directory), "utf8"));
|
|
33484
33632
|
if (typeof fields !== "object" || fields === null) {
|
|
33485
33633
|
return null;
|
|
33486
33634
|
}
|
|
@@ -33496,17 +33644,17 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
33496
33644
|
var SHARED_NOTE = "run.json";
|
|
33497
33645
|
function openTranscript(startedAt) {
|
|
33498
33646
|
mkdirSync12(runsDirectory(), { recursive: true });
|
|
33499
|
-
const path =
|
|
33647
|
+
const path = join21(runsDirectory(), `run-${startedAt}.log`);
|
|
33500
33648
|
return { path, fd: openSync2(path, "a") };
|
|
33501
33649
|
}
|
|
33502
33650
|
function profilePath(startedAt) {
|
|
33503
|
-
return
|
|
33651
|
+
return join21(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
33504
33652
|
}
|
|
33505
33653
|
function noteIsAbout(note, playing) {
|
|
33506
33654
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
33507
33655
|
}
|
|
33508
33656
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
33509
|
-
return
|
|
33657
|
+
return join21(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
33510
33658
|
}
|
|
33511
33659
|
function writeEditorRunNote(note) {
|
|
33512
33660
|
mkdirSync12(runsDirectory(), { recursive: true });
|
|
@@ -33517,7 +33665,7 @@ function writeEditorRunNote(note) {
|
|
|
33517
33665
|
}
|
|
33518
33666
|
function readEditorRunNote(projectPath) {
|
|
33519
33667
|
for (const directory of runtimeDirectories()) {
|
|
33520
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
33668
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join21(directory, "runs", "editor-run.json"));
|
|
33521
33669
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
33522
33670
|
return note;
|
|
33523
33671
|
}
|
|
@@ -33527,7 +33675,7 @@ function readEditorRunNote(projectPath) {
|
|
|
33527
33675
|
function editorNoteAt(path) {
|
|
33528
33676
|
let parsed;
|
|
33529
33677
|
try {
|
|
33530
|
-
parsed = JSON.parse(
|
|
33678
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
33531
33679
|
} catch {
|
|
33532
33680
|
return null;
|
|
33533
33681
|
}
|
|
@@ -33551,7 +33699,7 @@ function readRunRecord(projectPath) {
|
|
|
33551
33699
|
if (own !== null) {
|
|
33552
33700
|
return own;
|
|
33553
33701
|
}
|
|
33554
|
-
const shared = recordAt(
|
|
33702
|
+
const shared = recordAt(join21(directory, "runs", SHARED_NOTE));
|
|
33555
33703
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
33556
33704
|
return shared;
|
|
33557
33705
|
}
|
|
@@ -33563,13 +33711,13 @@ function everyRunRecord() {
|
|
|
33563
33711
|
for (const directory of runtimeDirectories()) {
|
|
33564
33712
|
let entries;
|
|
33565
33713
|
try {
|
|
33566
|
-
entries =
|
|
33714
|
+
entries = readdirSync8(join21(directory, "runs"));
|
|
33567
33715
|
} catch {
|
|
33568
33716
|
continue;
|
|
33569
33717
|
}
|
|
33570
33718
|
for (const entry of entries) {
|
|
33571
33719
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
33572
|
-
const record = recordAt(
|
|
33720
|
+
const record = recordAt(join21(directory, "runs", entry));
|
|
33573
33721
|
if (record !== null) {
|
|
33574
33722
|
found.push(record);
|
|
33575
33723
|
}
|
|
@@ -33581,7 +33729,7 @@ function everyRunRecord() {
|
|
|
33581
33729
|
function recordAt(path) {
|
|
33582
33730
|
let parsed;
|
|
33583
33731
|
try {
|
|
33584
|
-
parsed = JSON.parse(
|
|
33732
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
33585
33733
|
} catch {
|
|
33586
33734
|
return null;
|
|
33587
33735
|
}
|
|
@@ -33634,10 +33782,10 @@ async function runningAs(pid) {
|
|
|
33634
33782
|
const began = await startedAt(pid, null);
|
|
33635
33783
|
const when = began === null ? {} : { startedAt: began };
|
|
33636
33784
|
if (process.platform === "linux") {
|
|
33637
|
-
const raw =
|
|
33785
|
+
const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
33638
33786
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
33639
33787
|
}
|
|
33640
|
-
const args = (await
|
|
33788
|
+
const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
|
|
33641
33789
|
return args === "" ? null : { kind: "commandLine", text: args, ...when };
|
|
33642
33790
|
} catch {
|
|
33643
33791
|
return null;
|
|
@@ -33646,7 +33794,7 @@ async function runningAs(pid) {
|
|
|
33646
33794
|
async function listeningPid(port) {
|
|
33647
33795
|
try {
|
|
33648
33796
|
if (process.platform === "win32") {
|
|
33649
|
-
const { stdout } = await
|
|
33797
|
+
const { stdout } = await runHidden("netstat", ["-ano"], { timeout: 15000 });
|
|
33650
33798
|
return listeningPidInNetstat(stdout, port);
|
|
33651
33799
|
}
|
|
33652
33800
|
for (const [command, args, pattern] of [
|
|
@@ -33654,7 +33802,7 @@ async function listeningPid(port) {
|
|
|
33654
33802
|
["ss", ["-ltnpH", `sport = :${port}`], /pid=(\d+)/]
|
|
33655
33803
|
]) {
|
|
33656
33804
|
try {
|
|
33657
|
-
const said = (await
|
|
33805
|
+
const said = (await runHidden(command, [...args], { timeout: 15000 })).stdout.trim();
|
|
33658
33806
|
const found = pattern.exec(said);
|
|
33659
33807
|
if (found?.[1] !== undefined) {
|
|
33660
33808
|
return Number(found[1]);
|
|
@@ -33699,7 +33847,7 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
33699
33847
|
return Number.isNaN(when) ? null : when;
|
|
33700
33848
|
}
|
|
33701
33849
|
try {
|
|
33702
|
-
const said = (await
|
|
33850
|
+
const said = (await runHidden("ps", ["-p", String(pid), "-o", "lstart="], { timeout: 15000 })).stdout.trim();
|
|
33703
33851
|
const when = Date.parse(said);
|
|
33704
33852
|
return said === "" || Number.isNaN(when) ? null : when;
|
|
33705
33853
|
} catch {
|
|
@@ -33708,10 +33856,8 @@ async function startedAt(pid, windowsAnswer) {
|
|
|
33708
33856
|
}
|
|
33709
33857
|
async function windowsImage(pid) {
|
|
33710
33858
|
try {
|
|
33711
|
-
const { stdout: csv } = await
|
|
33712
|
-
|
|
33713
|
-
timeout: 15000,
|
|
33714
|
-
windowsHide: true
|
|
33859
|
+
const { stdout: csv } = await runHidden("tasklist", ["/FI", `PID eq ${pid}`, "/FO", "CSV", "/NH"], {
|
|
33860
|
+
timeout: 15000
|
|
33715
33861
|
});
|
|
33716
33862
|
const image = /^"([^"]+)"/.exec(csv.trim())?.[1];
|
|
33717
33863
|
return image === undefined ? { why: "named no process under that number" } : { image };
|
|
@@ -33741,7 +33887,7 @@ function whyNotTheRun(record, running, needed) {
|
|
|
33741
33887
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
33742
33888
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
33743
33889
|
}
|
|
33744
|
-
const engine = record.command === undefined ? null :
|
|
33890
|
+
const engine = record.command === undefined ? null : basename5(record.command);
|
|
33745
33891
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
33746
33892
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
33747
33893
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -33780,7 +33926,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
33780
33926
|
}
|
|
33781
33927
|
function clearRunRecord(projectPath) {
|
|
33782
33928
|
for (const directory of runtimeDirectories()) {
|
|
33783
|
-
const shared =
|
|
33929
|
+
const shared = join21(directory, "runs", SHARED_NOTE);
|
|
33784
33930
|
const sharedRecord = recordAt(shared);
|
|
33785
33931
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
33786
33932
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -33792,7 +33938,7 @@ function clearRunRecord(projectPath) {
|
|
|
33792
33938
|
}
|
|
33793
33939
|
function ageMs(path, now) {
|
|
33794
33940
|
try {
|
|
33795
|
-
return now -
|
|
33941
|
+
return now - statSync8(path).mtimeMs;
|
|
33796
33942
|
} catch {
|
|
33797
33943
|
return 0;
|
|
33798
33944
|
}
|
|
@@ -33805,7 +33951,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33805
33951
|
]));
|
|
33806
33952
|
let entries;
|
|
33807
33953
|
try {
|
|
33808
|
-
entries =
|
|
33954
|
+
entries = readdirSync8(directory);
|
|
33809
33955
|
} catch {
|
|
33810
33956
|
return;
|
|
33811
33957
|
}
|
|
@@ -33814,7 +33960,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33814
33960
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
33815
33961
|
continue;
|
|
33816
33962
|
}
|
|
33817
|
-
const path =
|
|
33963
|
+
const path = join21(directory, entry);
|
|
33818
33964
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
33819
33965
|
continue;
|
|
33820
33966
|
}
|
|
@@ -33825,7 +33971,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
33825
33971
|
}
|
|
33826
33972
|
|
|
33827
33973
|
// src/script-origins.ts
|
|
33828
|
-
import { join as
|
|
33974
|
+
import { join as join22 } from "node:path";
|
|
33829
33975
|
var UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
|
|
33830
33976
|
function unresolvedClassIn(message) {
|
|
33831
33977
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
@@ -33881,7 +34027,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
33881
34027
|
const ran = await runEngine(godotPath, [
|
|
33882
34028
|
"--headless",
|
|
33883
34029
|
"--log-file",
|
|
33884
|
-
|
|
34030
|
+
join22(logDir, "engine.log"),
|
|
33885
34031
|
"--path",
|
|
33886
34032
|
projectPath,
|
|
33887
34033
|
"--check-only",
|
|
@@ -33918,10 +34064,10 @@ function withoutListed(entries, errors) {
|
|
|
33918
34064
|
}
|
|
33919
34065
|
|
|
33920
34066
|
// src/script-profile.ts
|
|
33921
|
-
import { readFileSync as
|
|
34067
|
+
import { readFileSync as readFileSync20 } from "node:fs";
|
|
33922
34068
|
function readProfileFile(path) {
|
|
33923
34069
|
try {
|
|
33924
|
-
const parsed = JSON.parse(
|
|
34070
|
+
const parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
33925
34071
|
return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
|
|
33926
34072
|
} catch {
|
|
33927
34073
|
return null;
|
|
@@ -35056,6 +35202,11 @@ var TOOL_SPECS = [
|
|
|
35056
35202
|
ops: ["start"],
|
|
35057
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."
|
|
35058
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
|
+
},
|
|
35059
35210
|
savesIn: {
|
|
35060
35211
|
type: "string",
|
|
35061
35212
|
ops: ["start", "check"],
|
|
@@ -35211,7 +35362,7 @@ var TOOL_SPECS = [
|
|
|
35211
35362
|
name: "editor_rescan",
|
|
35212
35363
|
title: "Rescan the project",
|
|
35213
35364
|
effect: "additive",
|
|
35214
|
-
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.",
|
|
35215
35366
|
parameters: {
|
|
35216
35367
|
projectPath: PROJECT_PATH2,
|
|
35217
35368
|
timeoutMs: {
|
|
@@ -35289,13 +35440,13 @@ var TOOL_SPECS = [
|
|
|
35289
35440
|
pid: RUNNING_PID,
|
|
35290
35441
|
nodePath: {
|
|
35291
35442
|
type: "string",
|
|
35292
|
-
ops: ["tree", "text", "find", "rect", "property"],
|
|
35293
|
-
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.'
|
|
35294
35445
|
},
|
|
35295
35446
|
property: {
|
|
35296
35447
|
type: "string",
|
|
35297
35448
|
ops: ["property", "find"],
|
|
35298
|
-
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.'
|
|
35299
35450
|
},
|
|
35300
35451
|
depth: {
|
|
35301
35452
|
type: "integer",
|
|
@@ -35311,8 +35462,8 @@ var TOOL_SPECS = [
|
|
|
35311
35462
|
properties: {
|
|
35312
35463
|
type: "array",
|
|
35313
35464
|
items: { type: "string" },
|
|
35314
|
-
ops: ["tree"],
|
|
35315
|
-
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.'
|
|
35316
35467
|
},
|
|
35317
35468
|
className: {
|
|
35318
35469
|
type: "string",
|
|
@@ -35328,15 +35479,15 @@ var TOOL_SPECS = [
|
|
|
35328
35479
|
group: { type: "string", ops: ["find"], description: "find: a group the node is in." },
|
|
35329
35480
|
says: {
|
|
35330
35481
|
type: "string",
|
|
35331
|
-
ops: ["find"],
|
|
35332
|
-
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.'
|
|
35333
35484
|
},
|
|
35334
35485
|
limit: {
|
|
35335
35486
|
type: "integer",
|
|
35336
35487
|
minimum: 1,
|
|
35337
35488
|
maximum: 5000,
|
|
35338
|
-
ops: ["find", "text"],
|
|
35339
|
-
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."
|
|
35340
35491
|
},
|
|
35341
35492
|
includeHidden: {
|
|
35342
35493
|
type: "boolean",
|
|
@@ -35380,12 +35531,12 @@ var TOOL_SPECS = [
|
|
|
35380
35531
|
},
|
|
35381
35532
|
metrics: {
|
|
35382
35533
|
effect: "read",
|
|
35383
|
-
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
|
|
35534
|
+
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. physics_steps_per_second is the physics steps taken a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
|
|
35384
35535
|
requires: []
|
|
35385
35536
|
},
|
|
35386
35537
|
accessibility: {
|
|
35387
35538
|
effect: "read",
|
|
35388
|
-
summary:
|
|
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",
|
|
35389
35540
|
requires: []
|
|
35390
35541
|
}
|
|
35391
35542
|
},
|
|
@@ -35394,19 +35545,19 @@ var TOOL_SPECS = [
|
|
|
35394
35545
|
{
|
|
35395
35546
|
name: "runtime_invoke",
|
|
35396
35547
|
title: "Set or call in the running game",
|
|
35397
|
-
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.",
|
|
35398
35549
|
parameters: {
|
|
35399
35550
|
projectPath: RUNNING_PROJECT_PATH,
|
|
35400
35551
|
pid: RUNNING_PID,
|
|
35401
35552
|
nodePath: {
|
|
35402
35553
|
type: "string",
|
|
35403
35554
|
ops: ["set", "call"],
|
|
35404
|
-
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.'
|
|
35405
35556
|
},
|
|
35406
35557
|
property: {
|
|
35407
35558
|
type: "string",
|
|
35408
35559
|
ops: ["set"],
|
|
35409
|
-
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.'
|
|
35410
35561
|
},
|
|
35411
35562
|
value: {
|
|
35412
35563
|
ops: ["set"],
|
|
@@ -35539,6 +35690,13 @@ var TOOL_SPECS = [
|
|
|
35539
35690
|
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
35540
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."
|
|
35541
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
|
+
},
|
|
35542
35700
|
strength: {
|
|
35543
35701
|
type: "number",
|
|
35544
35702
|
minimum: 0,
|
|
@@ -35687,7 +35845,7 @@ var TOOL_SPECS = [
|
|
|
35687
35845
|
property: {
|
|
35688
35846
|
type: "string",
|
|
35689
35847
|
ops: ["until"],
|
|
35690
|
-
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.'
|
|
35691
35849
|
},
|
|
35692
35850
|
value: {
|
|
35693
35851
|
ops: ["until"],
|
|
@@ -35872,20 +36030,19 @@ function serverInstructions() {
|
|
|
35872
36030
|
}
|
|
35873
36031
|
|
|
35874
36032
|
// src/setup.ts
|
|
35875
|
-
import { spawnSync } from "node:child_process";
|
|
35876
36033
|
import { createHash as createHash4 } from "node:crypto";
|
|
35877
36034
|
import {
|
|
35878
36035
|
cpSync,
|
|
35879
|
-
existsSync as
|
|
36036
|
+
existsSync as existsSync17,
|
|
35880
36037
|
mkdirSync as mkdirSync13,
|
|
35881
|
-
readdirSync as
|
|
35882
|
-
readFileSync as
|
|
36038
|
+
readdirSync as readdirSync9,
|
|
36039
|
+
readFileSync as readFileSync21,
|
|
35883
36040
|
rmdirSync,
|
|
35884
36041
|
rmSync as rmSync11,
|
|
35885
|
-
statSync as
|
|
35886
|
-
writeFileSync as
|
|
36042
|
+
statSync as statSync9,
|
|
36043
|
+
writeFileSync as writeFileSync13
|
|
35887
36044
|
} from "node:fs";
|
|
35888
|
-
import { dirname as
|
|
36045
|
+
import { dirname as dirname16, join as join23 } from "node:path";
|
|
35889
36046
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
35890
36047
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
35891
36048
|
var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
|
|
@@ -35904,25 +36061,25 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
35904
36061
|
function addonsDigest(names, from) {
|
|
35905
36062
|
const hash = createHash4("sha256");
|
|
35906
36063
|
const walk = (directory, relative) => {
|
|
35907
|
-
for (const name of
|
|
36064
|
+
for (const name of readdirSync9(directory).sort()) {
|
|
35908
36065
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
35909
36066
|
continue;
|
|
35910
36067
|
}
|
|
35911
|
-
const path =
|
|
36068
|
+
const path = join23(directory, name);
|
|
35912
36069
|
const inside = `${relative}/${name}`;
|
|
35913
|
-
if (
|
|
36070
|
+
if (statSync9(path).isDirectory()) {
|
|
35914
36071
|
walk(path, inside);
|
|
35915
36072
|
} else {
|
|
35916
36073
|
hash.update(`${inside}
|
|
35917
36074
|
`);
|
|
35918
|
-
hash.update(
|
|
36075
|
+
hash.update(readFileSync21(path));
|
|
35919
36076
|
hash.update(`
|
|
35920
36077
|
`);
|
|
35921
36078
|
}
|
|
35922
36079
|
}
|
|
35923
36080
|
};
|
|
35924
36081
|
for (const name of names) {
|
|
35925
|
-
walk(
|
|
36082
|
+
walk(join23(from, name), name);
|
|
35926
36083
|
}
|
|
35927
36084
|
return hash.digest("hex");
|
|
35928
36085
|
}
|
|
@@ -35955,22 +36112,22 @@ function installedRuntimeDigest(projectPath) {
|
|
|
35955
36112
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
35956
36113
|
}
|
|
35957
36114
|
function digestMarker(projectPath, addon) {
|
|
35958
|
-
const marker =
|
|
35959
|
-
if (!
|
|
36115
|
+
const marker = join23(projectPath, "addons", addon, DIGEST_MARKER);
|
|
36116
|
+
if (!existsSync17(marker)) {
|
|
35960
36117
|
return null;
|
|
35961
36118
|
}
|
|
35962
|
-
const said =
|
|
36119
|
+
const said = readFileSync21(marker, "utf8").trim();
|
|
35963
36120
|
return said === "" ? null : said;
|
|
35964
36121
|
}
|
|
35965
36122
|
function shippedAddonsDirectory() {
|
|
35966
|
-
return
|
|
36123
|
+
return join23(dirname16(fileURLToPath3(import.meta.url)), "godot", "addons");
|
|
35967
36124
|
}
|
|
35968
36125
|
function installedAddonVersion(projectPath) {
|
|
35969
|
-
const marker =
|
|
35970
|
-
if (!
|
|
36126
|
+
const marker = join23(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
36127
|
+
if (!existsSync17(marker)) {
|
|
35971
36128
|
return null;
|
|
35972
36129
|
}
|
|
35973
|
-
const said =
|
|
36130
|
+
const said = readFileSync21(marker, "utf8").trim();
|
|
35974
36131
|
return said === "" ? null : said;
|
|
35975
36132
|
}
|
|
35976
36133
|
function loaderAmong(projectPath, named) {
|
|
@@ -35981,7 +36138,7 @@ function loaderAmong(projectPath, named) {
|
|
|
35981
36138
|
}
|
|
35982
36139
|
let source = null;
|
|
35983
36140
|
try {
|
|
35984
|
-
source =
|
|
36141
|
+
source = readFileSync21(join23(projectPath, path), "utf8");
|
|
35985
36142
|
} catch {}
|
|
35986
36143
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
35987
36144
|
return { name: entry, path, exists: true };
|
|
@@ -36004,7 +36161,7 @@ function trackedByGit(projectPath, wanted) {
|
|
|
36004
36161
|
if (wanted.length === 0) {
|
|
36005
36162
|
return new Set;
|
|
36006
36163
|
}
|
|
36007
|
-
const asked =
|
|
36164
|
+
const asked = spawnSyncHidden("git", ["ls-files", "-z", "--", ...wanted], {
|
|
36008
36165
|
cwd: projectPath,
|
|
36009
36166
|
encoding: "utf8"
|
|
36010
36167
|
});
|
|
@@ -36023,10 +36180,10 @@ function enabledPlugins(settings) {
|
|
|
36023
36180
|
function inspectProject(projectPath) {
|
|
36024
36181
|
const problems = [];
|
|
36025
36182
|
const addons = ADDONS.map((name) => {
|
|
36026
|
-
const directory =
|
|
36027
|
-
const marker =
|
|
36028
|
-
const installed =
|
|
36029
|
-
const version =
|
|
36183
|
+
const directory = join23(projectPath, "addons", name);
|
|
36184
|
+
const marker = join23(directory, VERSION_MARKER);
|
|
36185
|
+
const installed = existsSync17(directory);
|
|
36186
|
+
const version = existsSync17(marker) ? readFileSync21(marker, "utf8").trim() : null;
|
|
36030
36187
|
const current = version === SERVER_VERSION;
|
|
36031
36188
|
if (!installed) {
|
|
36032
36189
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -36035,7 +36192,7 @@ function inspectProject(projectPath) {
|
|
|
36035
36192
|
}
|
|
36036
36193
|
return { name, installed, version, current };
|
|
36037
36194
|
});
|
|
36038
|
-
const settings = parseProjectGodot(
|
|
36195
|
+
const settings = parseProjectGodot(readFileSync21(join23(projectPath, "project.godot"), "utf8"));
|
|
36039
36196
|
const pluginsEnabled = enabledPlugins(settings);
|
|
36040
36197
|
for (const name of EDITOR_PLUGINS) {
|
|
36041
36198
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -36051,7 +36208,7 @@ function inspectProject(projectPath) {
|
|
|
36051
36208
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
36052
36209
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
36053
36210
|
for (const [name, path] of named) {
|
|
36054
|
-
if (!
|
|
36211
|
+
if (!existsSync17(join23(projectPath, path))) {
|
|
36055
36212
|
if (!addonMissing(path)) {
|
|
36056
36213
|
problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
|
|
36057
36214
|
}
|
|
@@ -36101,8 +36258,7 @@ function argumentShape(schema) {
|
|
|
36101
36258
|
// src/server.ts
|
|
36102
36259
|
var UPDATE_NOTICE_EVERY = 500;
|
|
36103
36260
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
36104
|
-
var
|
|
36105
|
-
var __dirname2 = dirname16(fileURLToPath4(import.meta.url));
|
|
36261
|
+
var __dirname2 = dirname17(fileURLToPath4(import.meta.url));
|
|
36106
36262
|
var PARENT_ASKED_AFTER_MS = 5000;
|
|
36107
36263
|
function editorRestartTimeoutMs() {
|
|
36108
36264
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
@@ -36134,22 +36290,22 @@ function writtenFileRefusal(path, rule) {
|
|
|
36134
36290
|
if (/^(res|user):\/\//.test(path)) {
|
|
36135
36291
|
return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
|
|
36136
36292
|
}
|
|
36137
|
-
if (!
|
|
36293
|
+
if (!isAbsolute4(path)) {
|
|
36138
36294
|
return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
|
|
36139
36295
|
}
|
|
36140
36296
|
if (!rule.extensions.includes(extname3(path).toLowerCase())) {
|
|
36141
36297
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
36142
36298
|
}
|
|
36143
|
-
const directory = realPathOr(
|
|
36144
|
-
if (!
|
|
36145
|
-
return `${named}'s directory ${
|
|
36299
|
+
const directory = realPathOr(dirname17(path));
|
|
36300
|
+
if (!existsSync18(directory)) {
|
|
36301
|
+
return `${named}'s directory ${dirname17(path)} does not exist; ${what} creates no directories.`;
|
|
36146
36302
|
}
|
|
36147
|
-
const where =
|
|
36303
|
+
const where = join24(directory, basename6(path));
|
|
36148
36304
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
36149
36305
|
if (inside !== undefined) {
|
|
36150
36306
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
36151
36307
|
}
|
|
36152
|
-
const taken = rule.writes(where).find((file) =>
|
|
36308
|
+
const taken = rule.writes(where).find((file) => existsSync18(file));
|
|
36153
36309
|
if (taken !== undefined) {
|
|
36154
36310
|
return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
|
|
36155
36311
|
}
|
|
@@ -36199,6 +36355,10 @@ function captureDetail(payload) {
|
|
|
36199
36355
|
function patienceForFrames(frames, atLeast) {
|
|
36200
36356
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
36201
36357
|
}
|
|
36358
|
+
function withDefined(fields) {
|
|
36359
|
+
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
36360
|
+
}
|
|
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.";
|
|
36202
36362
|
function longerWaitNote(tool, op, waitedMs) {
|
|
36203
36363
|
const spec = TOOL_SPECS.find((one) => one.name === tool);
|
|
36204
36364
|
if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
|
|
@@ -36666,9 +36826,35 @@ function notRunNote(notRun, failFast) {
|
|
|
36666
36826
|
const cases = `${notRun} case${notRun === 1 ? "" : "s"}`;
|
|
36667
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.`;
|
|
36668
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
|
+
}
|
|
36669
36854
|
function exportAnswer(ending, asked, before, after) {
|
|
36670
36855
|
const { log } = ending;
|
|
36671
36856
|
const written = after !== null && after !== before;
|
|
36857
|
+
const notCopied = librariesNotCopied(log.all);
|
|
36672
36858
|
const verdict = {
|
|
36673
36859
|
exported: ending.exitCode === 0 && ending.failure === null && log.count("error") === 0 && written,
|
|
36674
36860
|
...asked,
|
|
@@ -36677,6 +36863,10 @@ function exportAnswer(ending, asked, before, after) {
|
|
|
36677
36863
|
failure: ending.failure ?? undefined,
|
|
36678
36864
|
errors: log.count("error"),
|
|
36679
36865
|
warnings: log.count("warning"),
|
|
36866
|
+
...notCopied.length === 0 ? {} : {
|
|
36867
|
+
librariesNotLoaded: notCopied,
|
|
36868
|
+
extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
|
|
36869
|
+
},
|
|
36680
36870
|
...foldedEntriesAnswer(log)
|
|
36681
36871
|
};
|
|
36682
36872
|
if (verdict.exported) {
|
|
@@ -36721,7 +36911,7 @@ var EDITOR_FREE_INSTEAD = {
|
|
|
36721
36911
|
scene_tree: "runtime_inspect tree reads the nodes of a game that is running, and needs no editor; scene_tree reads a scene file through the editor"
|
|
36722
36912
|
};
|
|
36723
36913
|
function editorAddonMissing(projectPath) {
|
|
36724
|
-
if (!
|
|
36914
|
+
if (!existsSync18(join24(projectPath, "project.godot"))) {
|
|
36725
36915
|
return null;
|
|
36726
36916
|
}
|
|
36727
36917
|
const report = inspectProject(projectPath);
|
|
@@ -37003,7 +37193,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
37003
37193
|
let projectGodot = null;
|
|
37004
37194
|
if (projectPath !== null) {
|
|
37005
37195
|
try {
|
|
37006
|
-
projectGodot =
|
|
37196
|
+
projectGodot = readFileSync22(join24(projectPath, "project.godot"), "utf8");
|
|
37007
37197
|
} catch {}
|
|
37008
37198
|
}
|
|
37009
37199
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -37029,7 +37219,7 @@ function projectSourceOf(projectPath) {
|
|
|
37029
37219
|
let source = null;
|
|
37030
37220
|
if (inside.ok) {
|
|
37031
37221
|
try {
|
|
37032
|
-
source =
|
|
37222
|
+
source = readFileSync22(inside.absolutePath, "utf8");
|
|
37033
37223
|
} catch {}
|
|
37034
37224
|
}
|
|
37035
37225
|
read.set(script, source);
|
|
@@ -37181,7 +37371,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
37181
37371
|
var ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd";
|
|
37182
37372
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
37183
37373
|
try {
|
|
37184
|
-
return settingKeys(
|
|
37374
|
+
return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
37185
37375
|
} catch {
|
|
37186
37376
|
return true;
|
|
37187
37377
|
}
|
|
@@ -37196,13 +37386,13 @@ function uncaughtNote(uncaught) {
|
|
|
37196
37386
|
}
|
|
37197
37387
|
function elsewhereIn(projectPath, asked) {
|
|
37198
37388
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37199
|
-
if (wanted === "" ||
|
|
37389
|
+
if (wanted === "" || existsSync18(join24(projectPath, wanted))) {
|
|
37200
37390
|
return "";
|
|
37201
37391
|
}
|
|
37202
37392
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
37203
37393
|
let near = [];
|
|
37204
37394
|
try {
|
|
37205
|
-
near =
|
|
37395
|
+
near = readdirSync10(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
|
|
37206
37396
|
} catch {
|
|
37207
37397
|
return "";
|
|
37208
37398
|
}
|
|
@@ -37287,23 +37477,23 @@ function scriptsToDiagnose(projectPath, given) {
|
|
|
37287
37477
|
var EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
37288
37478
|
function isDirectory(path) {
|
|
37289
37479
|
try {
|
|
37290
|
-
return
|
|
37480
|
+
return statSync10(path).isDirectory();
|
|
37291
37481
|
} catch {
|
|
37292
37482
|
return false;
|
|
37293
37483
|
}
|
|
37294
37484
|
}
|
|
37295
37485
|
function scriptsUnder(directory, prefix) {
|
|
37296
|
-
if (
|
|
37486
|
+
if (existsSync18(join24(directory, ".gdignore"))) {
|
|
37297
37487
|
return [];
|
|
37298
37488
|
}
|
|
37299
37489
|
const found = [];
|
|
37300
|
-
for (const entry of
|
|
37490
|
+
for (const entry of readdirSync10(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
37301
37491
|
if (entry.name.startsWith(".")) {
|
|
37302
37492
|
continue;
|
|
37303
37493
|
}
|
|
37304
37494
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
37305
37495
|
if (entry.isDirectory()) {
|
|
37306
|
-
found.push(...scriptsUnder(
|
|
37496
|
+
found.push(...scriptsUnder(join24(directory, entry.name), path));
|
|
37307
37497
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
37308
37498
|
found.push(path);
|
|
37309
37499
|
}
|
|
@@ -37311,7 +37501,7 @@ function scriptsUnder(directory, prefix) {
|
|
|
37311
37501
|
return found;
|
|
37312
37502
|
}
|
|
37313
37503
|
function hasMainScene(projectFile) {
|
|
37314
|
-
const scene = parseProjectGodot(
|
|
37504
|
+
const scene = parseProjectGodot(readFileSync22(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37315
37505
|
return typeof scene === "string" && scene !== "";
|
|
37316
37506
|
}
|
|
37317
37507
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -37319,13 +37509,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
37319
37509
|
return false;
|
|
37320
37510
|
}
|
|
37321
37511
|
try {
|
|
37322
|
-
return
|
|
37512
|
+
return readFileSync22(join24(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
37323
37513
|
} catch {
|
|
37324
37514
|
return false;
|
|
37325
37515
|
}
|
|
37326
37516
|
}
|
|
37327
37517
|
function editorPlaysHeadless(projectFile) {
|
|
37328
|
-
const runArgs = parseProjectGodot(
|
|
37518
|
+
const runArgs = parseProjectGodot(readFileSync22(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37329
37519
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37330
37520
|
}
|
|
37331
37521
|
function camelCased(params) {
|
|
@@ -37345,7 +37535,7 @@ var STATIC_LIST_HINT = { ttlMs: 5 * 60000, cacheScope: "private" };
|
|
|
37345
37535
|
class GodotServer {
|
|
37346
37536
|
stdio = null;
|
|
37347
37537
|
locator = new GodotLocator;
|
|
37348
|
-
operationsScript =
|
|
37538
|
+
operationsScript = join24(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37349
37539
|
godotBridge;
|
|
37350
37540
|
tools = buildToolDefinitions();
|
|
37351
37541
|
activeProcess = null;
|
|
@@ -37370,6 +37560,7 @@ class GodotServer {
|
|
|
37370
37560
|
launchedDropped = null;
|
|
37371
37561
|
closedHere = null;
|
|
37372
37562
|
inFlight = new Map;
|
|
37563
|
+
callsOnGames = new Set;
|
|
37373
37564
|
callsTaken = 0;
|
|
37374
37565
|
clientNoted = null;
|
|
37375
37566
|
parentCommand = null;
|
|
@@ -37601,14 +37792,14 @@ class GodotServer {
|
|
|
37601
37792
|
}
|
|
37602
37793
|
noteTheStart() {
|
|
37603
37794
|
const cwd = process.cwd();
|
|
37604
|
-
const mcpJson =
|
|
37795
|
+
const mcpJson = join24(cwd, ".mcp.json");
|
|
37605
37796
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
37606
37797
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
37607
37798
|
writeLifecycle("started", {
|
|
37608
37799
|
argv: process.argv.slice(1),
|
|
37609
37800
|
cwd,
|
|
37610
37801
|
project: this.ownProject,
|
|
37611
|
-
mcpJson:
|
|
37802
|
+
mcpJson: existsSync18(mcpJson) ? mcpJson : null
|
|
37612
37803
|
});
|
|
37613
37804
|
if (predecessor !== null) {
|
|
37614
37805
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -37749,7 +37940,7 @@ class GodotServer {
|
|
|
37749
37940
|
return checked.response;
|
|
37750
37941
|
}
|
|
37751
37942
|
const named = args["projectPath"];
|
|
37752
|
-
if (typeof named === "string" &&
|
|
37943
|
+
if (typeof named === "string" && existsSync18(join24(named, "project.godot"))) {
|
|
37753
37944
|
this.lastProjectPath = named;
|
|
37754
37945
|
}
|
|
37755
37946
|
this.updates.refresh();
|
|
@@ -38156,7 +38347,15 @@ class GodotServer {
|
|
|
38156
38347
|
property: readNonEmptyString(args, "property") ?? ""
|
|
38157
38348
|
}, { tool: "runtime_inspect", op, patience });
|
|
38158
38349
|
case "accessibility":
|
|
38159
|
-
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
|
+
}, {
|
|
38160
38359
|
tool: "runtime_inspect",
|
|
38161
38360
|
op,
|
|
38162
38361
|
patience: readPositiveNumber(args, "timeoutMs") ?? Math.max(30000, this.runtimeTimeoutMs())
|
|
@@ -38207,7 +38406,14 @@ class GodotServer {
|
|
|
38207
38406
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
38208
38407
|
}, { tool: "runtime_input", op });
|
|
38209
38408
|
}
|
|
38210
|
-
|
|
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
|
+
}
|
|
38211
38417
|
case "runtime_wait":
|
|
38212
38418
|
return await this.handleRuntimeWait(op, args);
|
|
38213
38419
|
case "debug_breakpoint": {
|
|
@@ -38307,8 +38513,8 @@ class GodotServer {
|
|
|
38307
38513
|
if (path === undefined) {
|
|
38308
38514
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
38309
38515
|
}
|
|
38310
|
-
const file =
|
|
38311
|
-
if (!
|
|
38516
|
+
const file = join24(path, "project.godot");
|
|
38517
|
+
if (!existsSync18(file)) {
|
|
38312
38518
|
return {
|
|
38313
38519
|
ok: false,
|
|
38314
38520
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -38354,7 +38560,7 @@ class GodotServer {
|
|
|
38354
38560
|
}
|
|
38355
38561
|
runEnvironment(args) {
|
|
38356
38562
|
const savesIn = readNonEmptyString(args, "savesIn");
|
|
38357
|
-
if (savesIn !== undefined && !
|
|
38563
|
+
if (savesIn !== undefined && !isAbsolute4(savesIn)) {
|
|
38358
38564
|
return {
|
|
38359
38565
|
ok: false,
|
|
38360
38566
|
response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
|
|
@@ -38938,11 +39144,11 @@ class GodotServer {
|
|
|
38938
39144
|
if (!engine.ok) {
|
|
38939
39145
|
return engine.response;
|
|
38940
39146
|
}
|
|
38941
|
-
const application = parseProjectGodot(
|
|
39147
|
+
const application = parseProjectGodot(readFileSync22(project.value.file, "utf8"))["application"] ?? {};
|
|
38942
39148
|
const name = application["config/name"];
|
|
38943
39149
|
const mainScene = application["run/main_scene"];
|
|
38944
39150
|
const info = {
|
|
38945
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
39151
|
+
name: typeof name === "string" && name !== "" ? name : basename6(project.value.path),
|
|
38946
39152
|
path: project.value.path,
|
|
38947
39153
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
38948
39154
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -38990,22 +39196,26 @@ class GodotServer {
|
|
|
38990
39196
|
if (!output.ok) {
|
|
38991
39197
|
return this.createErrorResponse(output.reason, PATH_SOLUTIONS);
|
|
38992
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
|
+
}
|
|
38993
39203
|
const engine = await this.engine();
|
|
38994
39204
|
if (!engine.ok) {
|
|
38995
39205
|
return engine.response;
|
|
38996
39206
|
}
|
|
38997
|
-
const folder =
|
|
39207
|
+
const folder = dirname17(output.absolutePath);
|
|
38998
39208
|
try {
|
|
38999
39209
|
mkdirSync14(folder, { recursive: true });
|
|
39000
39210
|
} catch (error) {
|
|
39001
|
-
return this.createErrorResponse(`Cannot create ${
|
|
39211
|
+
return this.createErrorResponse(`Cannot create ${dirname17(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
39002
39212
|
}
|
|
39003
39213
|
const debug = readBoolean(args, "debug") ?? false;
|
|
39004
39214
|
const exportLogs = scratchDirectory("export");
|
|
39005
39215
|
const exportArgs = [
|
|
39006
39216
|
"--headless",
|
|
39007
39217
|
"--log-file",
|
|
39008
|
-
|
|
39218
|
+
join24(exportLogs, "engine.log"),
|
|
39009
39219
|
"--path",
|
|
39010
39220
|
project.value.path,
|
|
39011
39221
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -39013,8 +39223,12 @@ class GodotServer {
|
|
|
39013
39223
|
output.absolutePath
|
|
39014
39224
|
];
|
|
39015
39225
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
39016
|
-
const writtenAt = () =>
|
|
39226
|
+
const writtenAt = () => existsSync18(output.absolutePath) ? statSync10(output.absolutePath).mtimeMs : null;
|
|
39017
39227
|
const before = writtenAt();
|
|
39228
|
+
const freed = freeLibraryCopies(project.value.path);
|
|
39229
|
+
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
39230
|
+
this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
|
|
39231
|
+
}
|
|
39018
39232
|
let ending;
|
|
39019
39233
|
try {
|
|
39020
39234
|
ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
|
|
@@ -39077,10 +39291,10 @@ class GodotServer {
|
|
|
39077
39291
|
if (movie !== null) {
|
|
39078
39292
|
return this.createErrorResponse(movie);
|
|
39079
39293
|
}
|
|
39080
|
-
const missing = paths.value.filter((path) => !
|
|
39294
|
+
const missing = paths.value.filter((path) => !existsSync18(join24(project.value.path, path.slice("res://".length))));
|
|
39081
39295
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
39082
39296
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
39083
|
-
const noRunner = !
|
|
39297
|
+
const noRunner = !existsSync18(join24(project.value.path, runner));
|
|
39084
39298
|
if (asked.length === 0) {
|
|
39085
39299
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
39086
39300
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -39171,21 +39385,21 @@ class GodotServer {
|
|
|
39171
39385
|
await untilGone([run.process.pid]);
|
|
39172
39386
|
}
|
|
39173
39387
|
await run.settled;
|
|
39174
|
-
const reportsDir =
|
|
39388
|
+
const reportsDir = join24(project.value.path, ".godot", ...ours.split("/"));
|
|
39175
39389
|
let report = null;
|
|
39176
39390
|
let reportProblem = null;
|
|
39177
39391
|
try {
|
|
39178
|
-
const written =
|
|
39392
|
+
const written = existsSync18(reportsDir) ? readdirSync10(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
39179
39393
|
const newest = written.at(-1);
|
|
39180
39394
|
if (newest !== undefined) {
|
|
39181
|
-
report = parseJUnit(
|
|
39395
|
+
report = parseJUnit(readFileSync22(join24(reportsDir, newest, "results.xml"), "utf8"));
|
|
39182
39396
|
}
|
|
39183
39397
|
} catch (error) {
|
|
39184
39398
|
reportProblem = errorMessage(error);
|
|
39185
39399
|
} finally {
|
|
39186
39400
|
discard(reportsDir, userData);
|
|
39187
39401
|
try {
|
|
39188
|
-
rmdirSync2(
|
|
39402
|
+
rmdirSync2(dirname17(reportsDir));
|
|
39189
39403
|
} catch {}
|
|
39190
39404
|
}
|
|
39191
39405
|
const engineEntries = foldedRepeats(run.log.select({
|
|
@@ -39354,7 +39568,7 @@ class GodotServer {
|
|
|
39354
39568
|
}
|
|
39355
39569
|
async godotVersion(godotPath) {
|
|
39356
39570
|
try {
|
|
39357
|
-
const { stdout } = await
|
|
39571
|
+
const { stdout } = await runHidden(godotPath, ["--version"], { timeout: 1e4 });
|
|
39358
39572
|
this.lastGodotVersion = stdout.trim();
|
|
39359
39573
|
return this.lastGodotVersion;
|
|
39360
39574
|
} catch (error) {
|
|
@@ -39415,6 +39629,10 @@ class GodotServer {
|
|
|
39415
39629
|
return markIfStale(answer, this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
|
|
39416
39630
|
}
|
|
39417
39631
|
async diagnosticsOf(args) {
|
|
39632
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
39633
|
+
if (waited !== null) {
|
|
39634
|
+
return { refusal: waited, perScript: false };
|
|
39635
|
+
}
|
|
39418
39636
|
const answer = await this.handleLSP("lsp_get_diagnostics", args);
|
|
39419
39637
|
const payload = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
39420
39638
|
const refused = this.languageServerRefusal(payload, "Diagnostics");
|
|
@@ -39445,11 +39663,11 @@ class GodotServer {
|
|
|
39445
39663
|
const cached = cachedClasses(projectPath);
|
|
39446
39664
|
const sourceOf = (resourcePath) => {
|
|
39447
39665
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39448
|
-
if (!contained.ok || !
|
|
39666
|
+
if (!contained.ok || !existsSync18(contained.absolutePath)) {
|
|
39449
39667
|
return null;
|
|
39450
39668
|
}
|
|
39451
39669
|
try {
|
|
39452
|
-
return
|
|
39670
|
+
return readFileSync22(contained.absolutePath, "utf8");
|
|
39453
39671
|
} catch {
|
|
39454
39672
|
return null;
|
|
39455
39673
|
}
|
|
@@ -39499,9 +39717,34 @@ class GodotServer {
|
|
|
39499
39717
|
const recorded = readClassNote(projectPath)?.shortListEditorPid;
|
|
39500
39718
|
return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
|
|
39501
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
|
+
}
|
|
39502
39744
|
noteAShortListEditor(projectPath) {
|
|
39503
39745
|
const note = readClassNote(projectPath);
|
|
39504
39746
|
writeClassNote(projectPath, {
|
|
39747
|
+
...note,
|
|
39505
39748
|
rebuiltWith: note?.rebuiltWith ?? [],
|
|
39506
39749
|
shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
|
|
39507
39750
|
});
|
|
@@ -39514,11 +39757,11 @@ class GodotServer {
|
|
|
39514
39757
|
}
|
|
39515
39758
|
sourceOfClass(projectPath, resourcePath) {
|
|
39516
39759
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39517
|
-
if (!contained.ok || !
|
|
39760
|
+
if (!contained.ok || !existsSync18(contained.absolutePath)) {
|
|
39518
39761
|
return null;
|
|
39519
39762
|
}
|
|
39520
39763
|
try {
|
|
39521
|
-
return
|
|
39764
|
+
return readFileSync22(contained.absolutePath, "utf8");
|
|
39522
39765
|
} catch {
|
|
39523
39766
|
return null;
|
|
39524
39767
|
}
|
|
@@ -39526,7 +39769,17 @@ class GodotServer {
|
|
|
39526
39769
|
async handleLSP(toolName, args) {
|
|
39527
39770
|
return handleLSPTool(await this.lsp(), toolName, args);
|
|
39528
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
|
+
}
|
|
39529
39778
|
async askScriptInfo(toolName, asked, args) {
|
|
39779
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
39780
|
+
if (waited !== null) {
|
|
39781
|
+
return waited;
|
|
39782
|
+
}
|
|
39530
39783
|
const answer = await this.handleLSP(toolName, args);
|
|
39531
39784
|
const refused = this.languageServerRefusal(asParams(JSON.parse(answer.content[0]?.text ?? "{}")), asked);
|
|
39532
39785
|
return refused === null ? answer : refused.refusal;
|
|
@@ -39666,7 +39919,7 @@ class GodotServer {
|
|
|
39666
39919
|
...Array.isArray(stack) ? {
|
|
39667
39920
|
stack: stack.map((frame) => ({
|
|
39668
39921
|
...frame,
|
|
39669
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
39922
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join24(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
39670
39923
|
}))
|
|
39671
39924
|
} : {}
|
|
39672
39925
|
});
|
|
@@ -39936,7 +40189,7 @@ class GodotServer {
|
|
|
39936
40189
|
return null;
|
|
39937
40190
|
}
|
|
39938
40191
|
try {
|
|
39939
|
-
return settingKeys(
|
|
40192
|
+
return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8"));
|
|
39940
40193
|
} catch {
|
|
39941
40194
|
return null;
|
|
39942
40195
|
}
|
|
@@ -40201,6 +40454,7 @@ class GodotServer {
|
|
|
40201
40454
|
}
|
|
40202
40455
|
async openAnEditor(engine, projectPath, ports, hidden) {
|
|
40203
40456
|
clearEditorLog(projectPath);
|
|
40457
|
+
freeLibraryCopies(projectPath);
|
|
40204
40458
|
const launched = await launchOutsideTheTree({
|
|
40205
40459
|
command: engine,
|
|
40206
40460
|
args: editorArguments(projectPath, ports, editorLogPath(projectPath), hidden),
|
|
@@ -40251,6 +40505,17 @@ class GodotServer {
|
|
|
40251
40505
|
if (sceneToRun && !sceneToRun.ok) {
|
|
40252
40506
|
return this.createErrorResponse(sceneToRun.reason, PATH_SOLUTIONS);
|
|
40253
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
|
+
}
|
|
40254
40519
|
const given = this.gameArguments(args);
|
|
40255
40520
|
if (!given.ok) {
|
|
40256
40521
|
return given.response;
|
|
@@ -40267,13 +40532,13 @@ class GodotServer {
|
|
|
40267
40532
|
if (!asked.ok) {
|
|
40268
40533
|
return asked.response;
|
|
40269
40534
|
}
|
|
40270
|
-
if (!sceneToRun && !hasMainScene(project.value.file)) {
|
|
40535
|
+
if (exported === null && !sceneToRun && !hasMainScene(project.value.file)) {
|
|
40271
40536
|
return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
|
|
40272
40537
|
"Pass scene to run one scene",
|
|
40273
40538
|
"Choose the main scene with project_settings set_main_scene"
|
|
40274
40539
|
]);
|
|
40275
40540
|
}
|
|
40276
|
-
const engine = await this.engine();
|
|
40541
|
+
const engine = exported?.ok === true ? { ok: true, value: exported.absolutePath } : await this.engine();
|
|
40277
40542
|
if (!engine.ok) {
|
|
40278
40543
|
return engine.response;
|
|
40279
40544
|
}
|
|
@@ -40311,11 +40576,11 @@ class GodotServer {
|
|
|
40311
40576
|
const editorWouldPlay = !headless || editorPlaysHeadless(project.value.file);
|
|
40312
40577
|
const silent = resolveSilent(args["silent"], process.env) && !headless;
|
|
40313
40578
|
const profiled = readBoolean(args, "profile") === true;
|
|
40314
|
-
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) {
|
|
40315
40580
|
return await this.playThroughEditor(sceneArgument, refreshed.value, scanned, project.value.path, alreadyPlaying, runtimeWaitMs, ended, profiled);
|
|
40316
40581
|
}
|
|
40317
40582
|
const cmdArgs = runArguments({
|
|
40318
|
-
projectPath: project.value.path,
|
|
40583
|
+
projectPath: exported === null ? project.value.path : null,
|
|
40319
40584
|
headless,
|
|
40320
40585
|
scene: sceneArgument,
|
|
40321
40586
|
userArgs: given.value,
|
|
@@ -40323,7 +40588,7 @@ class GodotServer {
|
|
|
40323
40588
|
silent
|
|
40324
40589
|
});
|
|
40325
40590
|
this.logDebug(`Running Godot project: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
40326
|
-
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);
|
|
40327
40592
|
if ("error" in started) {
|
|
40328
40593
|
return this.createErrorResponse(`The game could not be started: ${started.error}`, [
|
|
40329
40594
|
"editor_status says which Godot this server runs and whether it answers"
|
|
@@ -40332,7 +40597,7 @@ class GodotServer {
|
|
|
40332
40597
|
started.announcedBefore = alreadyPlaying;
|
|
40333
40598
|
started.bootScene = runtimeWaitMs.scene;
|
|
40334
40599
|
this.activeProcess = started;
|
|
40335
|
-
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}` : "";
|
|
40336
40601
|
const endedForThis = endedToStartThis(ended);
|
|
40337
40602
|
const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
|
|
40338
40603
|
const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
|
|
@@ -40342,6 +40607,7 @@ class GodotServer {
|
|
|
40342
40607
|
through: "gdharness",
|
|
40343
40608
|
alsoRunning: stranded.length === 0 ? undefined : stranded.map((one) => one.pid),
|
|
40344
40609
|
pid: started.pid,
|
|
40610
|
+
executable: exported?.ok === true ? exported.relativePath : undefined,
|
|
40345
40611
|
arguments: cmdArgs,
|
|
40346
40612
|
argsNote: engineFlagsNote(given.value),
|
|
40347
40613
|
transcript: started.transcript,
|
|
@@ -40353,13 +40619,13 @@ class GodotServer {
|
|
|
40353
40619
|
endedPreviousRun: endedPreviousRun(ended),
|
|
40354
40620
|
previousRunLeft: previousRunLeft(ended),
|
|
40355
40621
|
...previousRunHow(ended),
|
|
40356
|
-
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),
|
|
40357
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}`
|
|
40358
40624
|
});
|
|
40359
40625
|
}
|
|
40360
40626
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
40361
40627
|
const budgetMs = budget.ms;
|
|
40362
|
-
if (!
|
|
40628
|
+
if (!existsSync18(join24(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
40363
40629
|
return runtimeVerdict(null, {
|
|
40364
40630
|
addon: false,
|
|
40365
40631
|
budgetMs,
|
|
@@ -40444,7 +40710,7 @@ class GodotServer {
|
|
|
40444
40710
|
}
|
|
40445
40711
|
let announcedAt;
|
|
40446
40712
|
try {
|
|
40447
|
-
announcedAt =
|
|
40713
|
+
announcedAt = statSync10(endpoint.file).mtimeMs;
|
|
40448
40714
|
} catch {
|
|
40449
40715
|
return;
|
|
40450
40716
|
}
|
|
@@ -40638,7 +40904,7 @@ class GodotServer {
|
|
|
40638
40904
|
return 0;
|
|
40639
40905
|
}
|
|
40640
40906
|
try {
|
|
40641
|
-
return reportAlreadyInTranscript(
|
|
40907
|
+
return reportAlreadyInTranscript(readFileSync22(game.transcript), readFileSync22(report));
|
|
40642
40908
|
} catch (error) {
|
|
40643
40909
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
40644
40910
|
return 0;
|
|
@@ -40793,7 +41059,10 @@ class GodotServer {
|
|
|
40793
41059
|
return gone ? "gone" : "lingering";
|
|
40794
41060
|
}
|
|
40795
41061
|
spawnGame(godotPath, cmdArgs, env) {
|
|
40796
|
-
const child =
|
|
41062
|
+
const child = spawnHidden(godotPath, cmdArgs, {
|
|
41063
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
41064
|
+
...env ? { env } : {}
|
|
41065
|
+
});
|
|
40797
41066
|
const cancelled = callSignal();
|
|
40798
41067
|
if (cancelled !== undefined) {
|
|
40799
41068
|
const end = () => {
|
|
@@ -40866,7 +41135,7 @@ class GodotServer {
|
|
|
40866
41135
|
});
|
|
40867
41136
|
return Object.assign(started, { settled });
|
|
40868
41137
|
}
|
|
40869
|
-
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false) {
|
|
41138
|
+
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop, profiled = false, debugged = true) {
|
|
40870
41139
|
const startedAt = Date.now();
|
|
40871
41140
|
const transcript = openTranscript(startedAt);
|
|
40872
41141
|
closeSync2(transcript.fd);
|
|
@@ -40882,7 +41151,7 @@ class GodotServer {
|
|
|
40882
41151
|
projectPath,
|
|
40883
41152
|
...this.servedBy(),
|
|
40884
41153
|
...profile === undefined ? {} : { profile },
|
|
40885
|
-
breakpoints: this.breakpointsForAGame(projectPath)
|
|
41154
|
+
...debugged ? { breakpoints: this.breakpointsForAGame(projectPath) } : { debugged: false }
|
|
40886
41155
|
},
|
|
40887
41156
|
...desktop === undefined ? {} : { desktop }
|
|
40888
41157
|
});
|
|
@@ -41318,7 +41587,7 @@ class GodotServer {
|
|
|
41318
41587
|
}
|
|
41319
41588
|
const going = await this.runStillGoing(run);
|
|
41320
41589
|
const projectPath = run.projectPath;
|
|
41321
|
-
const projectGone = projectPath !== null && !
|
|
41590
|
+
const projectGone = projectPath !== null && !existsSync18(join24(projectPath, "project.godot"));
|
|
41322
41591
|
if (projectGone && callersName !== undefined) {
|
|
41323
41592
|
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
41324
41593
|
}
|
|
@@ -41606,7 +41875,7 @@ class GodotServer {
|
|
|
41606
41875
|
notes.push(endedWithoutACode({
|
|
41607
41876
|
throughEditor: run.throughEditor,
|
|
41608
41877
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
41609
|
-
couldAnnounce: run.projectPath !== null &&
|
|
41878
|
+
couldAnnounce: run.projectPath !== null && existsSync18(join24(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
41610
41879
|
endedHere: typeof run.endedHere === "string",
|
|
41611
41880
|
pickedUp: run.pickedUp === true
|
|
41612
41881
|
}));
|
|
@@ -41846,8 +42115,8 @@ class GodotServer {
|
|
|
41846
42115
|
try {
|
|
41847
42116
|
const status = asParams(await this.godotBridge.invokeTool("scan_status", {}));
|
|
41848
42117
|
return status["scanning"] === true || status["importing"] === true;
|
|
41849
|
-
} catch {
|
|
41850
|
-
return
|
|
42118
|
+
} catch (error) {
|
|
42119
|
+
return error instanceof BridgeTimeout;
|
|
41851
42120
|
}
|
|
41852
42121
|
}
|
|
41853
42122
|
async handleRescanFilesystem(args) {
|
|
@@ -41858,7 +42127,7 @@ class GodotServer {
|
|
|
41858
42127
|
let writtenBefore = null;
|
|
41859
42128
|
let blind = { unseen: [] };
|
|
41860
42129
|
let waitedForEditorMs = 0;
|
|
41861
|
-
let
|
|
42130
|
+
let startUnanswered = false;
|
|
41862
42131
|
let completedBefore;
|
|
41863
42132
|
let uidsReread = [];
|
|
41864
42133
|
let uidsDuplicatedOnDisk = [];
|
|
@@ -41875,7 +42144,11 @@ class GodotServer {
|
|
|
41875
42144
|
before = projectPath === "" ? null : cachedClasses(projectPath);
|
|
41876
42145
|
writtenBefore = projectPath === "" ? null : cacheWrittenAt(projectPath);
|
|
41877
42146
|
blind = await this.classesTheEditorCannotSee(args);
|
|
41878
|
-
first = await this.
|
|
42147
|
+
const first = await this.askViaBridge("rescan_filesystem", args);
|
|
42148
|
+
if (first instanceof BridgeTimeout) {
|
|
42149
|
+
startUnanswered = true;
|
|
42150
|
+
break;
|
|
42151
|
+
}
|
|
41879
42152
|
if (first.isError) {
|
|
41880
42153
|
return first;
|
|
41881
42154
|
}
|
|
@@ -41898,7 +42171,17 @@ class GodotServer {
|
|
|
41898
42171
|
let idleSince = null;
|
|
41899
42172
|
while (Date.now() - started < timeoutMs) {
|
|
41900
42173
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
41901
|
-
|
|
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
|
+
}
|
|
41902
42185
|
const pending = Boolean(status["pending"]) && Date.now() - started < SCAN_START_MS;
|
|
41903
42186
|
const completed = readNumber(status, "scansCompleted");
|
|
41904
42187
|
const notYetOver = completedBefore !== undefined && completed !== undefined && completed <= completedBefore;
|
|
@@ -41981,12 +42264,20 @@ class GodotServer {
|
|
|
41981
42264
|
}
|
|
41982
42265
|
const checked = busy ? { unseen: [] } : await this.classesTheEditorCannotSee(args);
|
|
41983
42266
|
const unseen = checked.unseen;
|
|
41984
|
-
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
|
+
}
|
|
41985
42273
|
const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
|
|
41986
42274
|
const stillGone = lost.filter((name) => !restored.includes(name));
|
|
41987
42275
|
const notes = [];
|
|
41988
42276
|
if (busy) {
|
|
41989
|
-
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
|
+
}
|
|
41990
42281
|
} else if (stillGone.length > 0) {
|
|
41991
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.");
|
|
41992
42283
|
} else if (lost.length > 0) {
|
|
@@ -42104,8 +42395,9 @@ class GodotServer {
|
|
|
42104
42395
|
const droppedByTheEditor = lost.filter((name) => !cameBack.includes(name));
|
|
42105
42396
|
const seenLosing = droppedByTheEditor.length > 0 && this.godotBridge.isConnected();
|
|
42106
42397
|
writeClassNote(projectPath, {
|
|
42398
|
+
...noted,
|
|
42107
42399
|
rebuiltWith: [...after.keys()].sort(),
|
|
42108
|
-
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } :
|
|
42400
|
+
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : {}
|
|
42109
42401
|
});
|
|
42110
42402
|
const notes = [];
|
|
42111
42403
|
if (seenLosing) {
|
|
@@ -42171,7 +42463,7 @@ class GodotServer {
|
|
|
42171
42463
|
if (!scriptPath.ok) {
|
|
42172
42464
|
return this.createErrorResponse(scriptPath.reason);
|
|
42173
42465
|
}
|
|
42174
|
-
if (!
|
|
42466
|
+
if (!existsSync18(scriptPath.absolutePath)) {
|
|
42175
42467
|
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
42176
42468
|
}
|
|
42177
42469
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
@@ -42179,6 +42471,10 @@ class GodotServer {
|
|
|
42179
42471
|
if (moveTo !== null && !moveTo.ok) {
|
|
42180
42472
|
return this.createErrorResponse(moveTo.reason);
|
|
42181
42473
|
}
|
|
42474
|
+
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
42475
|
+
if (waited !== null) {
|
|
42476
|
+
return waited;
|
|
42477
|
+
}
|
|
42182
42478
|
const languageServer = {
|
|
42183
42479
|
problem: null,
|
|
42184
42480
|
initialized: false
|
|
@@ -42421,6 +42717,16 @@ class GodotServer {
|
|
|
42421
42717
|
]);
|
|
42422
42718
|
}
|
|
42423
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) {
|
|
42424
42730
|
if (!this.godotBridge.isConnected()) {
|
|
42425
42731
|
const alternative = askedAs !== undefined && Object.hasOwn(EDITOR_FREE_INSTEAD, askedAs) ? EDITOR_FREE_INSTEAD[askedAs] : undefined;
|
|
42426
42732
|
const refused = await this.noEditorYet(args, alternative === undefined ? [] : [alternative]);
|
|
@@ -42439,6 +42745,9 @@ class GodotServer {
|
|
|
42439
42745
|
try {
|
|
42440
42746
|
return this.jsonTextResponse(markIfStale(await this.godotBridge.invokeTool(toolName, args), this.godotBridge.getStatus().addonVersion, SERVER_VERSION, this.godotBridge.getStatus().addonDigest, shippedEditorDigest(), this.editorCodeOnDisk()));
|
|
42441
42747
|
} catch (error) {
|
|
42748
|
+
if (error instanceof BridgeTimeout) {
|
|
42749
|
+
return error;
|
|
42750
|
+
}
|
|
42442
42751
|
const message = errorMessage(error);
|
|
42443
42752
|
if (message.includes(`Unknown tool: ${toolName}`)) {
|
|
42444
42753
|
const status = this.godotBridge.getStatus();
|
|
@@ -42499,7 +42808,7 @@ class GodotServer {
|
|
|
42499
42808
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
42500
42809
|
const run = await this.currentRun();
|
|
42501
42810
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
42502
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
42811
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync18(join24(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
42503
42812
|
return null;
|
|
42504
42813
|
}
|
|
42505
42814
|
const already = runtimesAnnounced().running;
|
|
@@ -42508,7 +42817,7 @@ class GodotServer {
|
|
|
42508
42817
|
return !run.announcedBefore.has(one.pid);
|
|
42509
42818
|
}
|
|
42510
42819
|
try {
|
|
42511
|
-
return
|
|
42820
|
+
return statSync10(one.file).mtimeMs >= run.startedAt;
|
|
42512
42821
|
} catch {
|
|
42513
42822
|
return false;
|
|
42514
42823
|
}
|
|
@@ -42584,12 +42893,25 @@ class GodotServer {
|
|
|
42584
42893
|
}
|
|
42585
42894
|
}
|
|
42586
42895
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
42587
|
-
const screenshotPath = screenshotDir ?
|
|
42896
|
+
const screenshotPath = screenshotDir ? join24(screenshotDir, "capture.png") : null;
|
|
42588
42897
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
42589
42898
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
42590
42899
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|
|
42591
42900
|
|
|
42592
42901
|
${staleNote}`);
|
|
42902
|
+
const onGame = {
|
|
42903
|
+
pid: choice.endpoint.pid,
|
|
42904
|
+
what: `${asked.tool} ${asked.op}`,
|
|
42905
|
+
alongside: new Set
|
|
42906
|
+
};
|
|
42907
|
+
for (const other of this.callsOnGames) {
|
|
42908
|
+
if (other.pid === onGame.pid) {
|
|
42909
|
+
other.alongside.add(onGame.what);
|
|
42910
|
+
onGame.alongside.add(other.what);
|
|
42911
|
+
}
|
|
42912
|
+
}
|
|
42913
|
+
this.callsOnGames.add(onGame);
|
|
42914
|
+
const alongside = () => onGame.alongside.size === 0 ? {} : { ranAlongside: [...onGame.alongside], alongsideNote: ALONGSIDE_NOTE };
|
|
42593
42915
|
try {
|
|
42594
42916
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
42595
42917
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
@@ -42600,7 +42922,8 @@ ${staleNote}`);
|
|
|
42600
42922
|
waitedMs: timeoutMs,
|
|
42601
42923
|
note: `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}`,
|
|
42602
42924
|
...answeredBy,
|
|
42603
|
-
...stale
|
|
42925
|
+
...stale,
|
|
42926
|
+
...alongside()
|
|
42604
42927
|
});
|
|
42605
42928
|
}
|
|
42606
42929
|
if (!reply.ok) {
|
|
@@ -42608,7 +42931,7 @@ ${staleNote}`);
|
|
|
42608
42931
|
}
|
|
42609
42932
|
const { id: _id, ...payload } = reply.payload;
|
|
42610
42933
|
if (!expectsScreenshot) {
|
|
42611
|
-
return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale });
|
|
42934
|
+
return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale, ...alongside() });
|
|
42612
42935
|
}
|
|
42613
42936
|
const returnedPath = readString(payload, "path");
|
|
42614
42937
|
if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
|
|
@@ -42625,18 +42948,22 @@ ${staleNote}`);
|
|
|
42625
42948
|
return this.createErrorResponse(`The capture was taken and could not be saved at ${saveTo}: ${errorMessage(error)}`);
|
|
42626
42949
|
}
|
|
42627
42950
|
}
|
|
42951
|
+
const ranWith = [...onGame.alongside];
|
|
42628
42952
|
return {
|
|
42629
42953
|
content: [
|
|
42630
42954
|
{
|
|
42631
42955
|
type: "text",
|
|
42632
|
-
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${
|
|
42956
|
+
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${ranWith.length === 0 ? "" : `
|
|
42957
|
+
|
|
42958
|
+
Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}${staleNote === undefined ? "" : `
|
|
42633
42959
|
|
|
42634
42960
|
${staleNote}`}`
|
|
42635
42961
|
},
|
|
42636
|
-
{ type: "image", data:
|
|
42962
|
+
{ type: "image", data: readFileSync22(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
42637
42963
|
]
|
|
42638
42964
|
};
|
|
42639
42965
|
} finally {
|
|
42966
|
+
this.callsOnGames.delete(onGame);
|
|
42640
42967
|
discard(screenshotDir);
|
|
42641
42968
|
}
|
|
42642
42969
|
}
|