gdharness 1.2.36 → 1.2.38
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 +918 -439
- package/build/godot/addons/gdharness_editor/headless_check.gd +411 -0
- package/build/godot/addons/gdharness_editor/headless_check.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +3 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +408 -10
- package/build/godot/addons/gdharness_editor/serialisation.gd +103 -5
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +88 -25
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +68 -19
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +9 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +3 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +103 -5
- package/build/godot/operations/editor_tools.gd +57 -0
- package/build/godot/operations/gdscript_analysis.gd +26 -2
- package/build/godot/operations/gdscript_authoring.gd +5 -2
- package/build/godot/operations/godot_operations.gd +58 -1
- package/build/godot/operations/property_values.gd +3 -0
- package/build/godot/operations/serialisation.gd +103 -5
- package/build/index.js +838 -366
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -8181,6 +8181,9 @@ class GameLog {
|
|
|
8181
8181
|
count(severity) {
|
|
8182
8182
|
return this.entries.filter((entry) => entry.severity === severity).length;
|
|
8183
8183
|
}
|
|
8184
|
+
crashReport() {
|
|
8185
|
+
return this.entries.find((entry) => CRASH.test(entry.text));
|
|
8186
|
+
}
|
|
8184
8187
|
everything() {
|
|
8185
8188
|
return this.entries;
|
|
8186
8189
|
}
|
|
@@ -8260,21 +8263,98 @@ function runEngine(executable, args, options = {}) {
|
|
|
8260
8263
|
});
|
|
8261
8264
|
});
|
|
8262
8265
|
}
|
|
8266
|
+
function exitCodeText(code) {
|
|
8267
|
+
if (code === null) {
|
|
8268
|
+
return "exit code unknown";
|
|
8269
|
+
}
|
|
8270
|
+
if (code < 3221225472) {
|
|
8271
|
+
return `exit code ${code}`;
|
|
8272
|
+
}
|
|
8273
|
+
const hex = `0x${code.toString(16).toUpperCase()}`;
|
|
8274
|
+
return `exit code ${hex}, ${WINDOWS_STATUSES.get(code) ?? "a Windows error status"}`;
|
|
8275
|
+
}
|
|
8263
8276
|
function howItEnded(run) {
|
|
8264
8277
|
if (run.failure !== null) {
|
|
8265
8278
|
return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
|
|
8266
8279
|
}
|
|
8267
|
-
return run.exitSignal === null ?
|
|
8280
|
+
return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}`;
|
|
8268
8281
|
}
|
|
8282
|
+
var WINDOWS_STATUSES;
|
|
8269
8283
|
var init_engine_run = __esm(() => {
|
|
8270
8284
|
init_call_signal();
|
|
8271
8285
|
init_game_log();
|
|
8272
8286
|
init_spawn();
|
|
8287
|
+
WINDOWS_STATUSES = new Map([
|
|
8288
|
+
[3221225477, "an access violation, so it crashed"],
|
|
8289
|
+
[3221225501, "an illegal instruction, so it crashed"],
|
|
8290
|
+
[3221225620, "an integer division by zero, so it crashed"],
|
|
8291
|
+
[3221225725, "a stack overflow, so it crashed"],
|
|
8292
|
+
[3221225781, "a library it needs was not found, so it never started"],
|
|
8293
|
+
[3221225786, "its console was closed or sent Ctrl+C"],
|
|
8294
|
+
[3221226356, "a corrupted heap, so it crashed"],
|
|
8295
|
+
[3221226505, "a stack buffer overrun or a fast fail, so it crashed"]
|
|
8296
|
+
]);
|
|
8273
8297
|
});
|
|
8274
8298
|
|
|
8275
|
-
// src/
|
|
8276
|
-
import { existsSync as existsSync5, readdirSync as readdirSync3,
|
|
8299
|
+
// src/doc-cache.ts
|
|
8300
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
8301
|
+
import { homedir as homedir3 } from "node:os";
|
|
8277
8302
|
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute2, join as join5 } from "node:path";
|
|
8303
|
+
import process5 from "node:process";
|
|
8304
|
+
function editorCacheDirectory(godotPath, platform = process5.platform, env = process5.env, home = homedir3()) {
|
|
8305
|
+
const beside = dirname4(godotPath);
|
|
8306
|
+
const bundle = platform === "darwin" && basename3(beside) === "MacOS" && basename3(dirname4(beside)) === "Contents" ? dirname4(dirname4(dirname4(beside))) : null;
|
|
8307
|
+
for (const folder of bundle === null ? [beside] : [beside, bundle]) {
|
|
8308
|
+
if (existsSync5(join5(folder, "._sc_")) || existsSync5(join5(folder, "_sc_"))) {
|
|
8309
|
+
return join5(folder, "editor_data", "cache");
|
|
8310
|
+
}
|
|
8311
|
+
}
|
|
8312
|
+
if (platform === "win32") {
|
|
8313
|
+
const set = (name) => env[name] === "" ? undefined : env[name];
|
|
8314
|
+
return join5(set("LOCALAPPDATA") ?? set("TEMP") ?? join5(home, "AppData", "Local"), "Godot");
|
|
8315
|
+
}
|
|
8316
|
+
if (platform === "darwin") {
|
|
8317
|
+
return join5(home, "Library", "Caches", "Godot");
|
|
8318
|
+
}
|
|
8319
|
+
const xdg = env["XDG_CACHE_HOME"];
|
|
8320
|
+
return join5(xdg !== undefined && isAbsolute2(xdg) ? xdg : join5(home, ".cache"), "godot");
|
|
8321
|
+
}
|
|
8322
|
+
function docCachesWritten(directory) {
|
|
8323
|
+
const written = new Map;
|
|
8324
|
+
let names;
|
|
8325
|
+
try {
|
|
8326
|
+
names = readdirSync3(directory);
|
|
8327
|
+
} catch {
|
|
8328
|
+
return written;
|
|
8329
|
+
}
|
|
8330
|
+
for (const name of names.filter((one) => CACHE_FILE.test(one))) {
|
|
8331
|
+
try {
|
|
8332
|
+
written.set(join5(directory, name), statSync4(join5(directory, name)).mtimeMs);
|
|
8333
|
+
} catch {}
|
|
8334
|
+
}
|
|
8335
|
+
return written;
|
|
8336
|
+
}
|
|
8337
|
+
function docCacheCrash(ran, before, after, what) {
|
|
8338
|
+
const report = ran.log.crashReport();
|
|
8339
|
+
if (ran.exitCode === 0 && ran.exitSignal === null && report === undefined || ran.failure !== null) {
|
|
8340
|
+
return null;
|
|
8341
|
+
}
|
|
8342
|
+
const rewritten = [...after].find(([path, at]) => before.get(path) !== at)?.[0];
|
|
8343
|
+
if (rewritten === undefined) {
|
|
8344
|
+
return null;
|
|
8345
|
+
}
|
|
8346
|
+
const said = report === undefined ? "" : `, and its crash handler printed "${report.text}"`;
|
|
8347
|
+
return `The first ${what} ended badly (${howItEnded(ran)}${said}) after rebuilding the editor's class reference ` + `cache, ${rewritten}. Godot crashes as an import or an export exits when it finds that cache ` + "stale, which it is whenever another build of the same minor version wrote it last, such as 4.7.1 " + `beside 4.7.2, and the crashing run writes a fresh one. So the ${what} was run again, and this answer ` + "is from that second run.";
|
|
8348
|
+
}
|
|
8349
|
+
var CACHE_FILE;
|
|
8350
|
+
var init_doc_cache = __esm(() => {
|
|
8351
|
+
init_engine_run();
|
|
8352
|
+
CACHE_FILE = /^editor_doc_cache-\d+\.\d+\.res$/;
|
|
8353
|
+
});
|
|
8354
|
+
|
|
8355
|
+
// src/library-copies.ts
|
|
8356
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
|
|
8357
|
+
import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute3, join as join6 } from "node:path";
|
|
8278
8358
|
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
8279
8359
|
const setAside = [];
|
|
8280
8360
|
const swept = [];
|
|
@@ -8282,26 +8362,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
8282
8362
|
return { setAside, swept };
|
|
8283
8363
|
}
|
|
8284
8364
|
for (const library of windowsLibraries(projectPath)) {
|
|
8285
|
-
const folder =
|
|
8286
|
-
const copy = `~${
|
|
8287
|
-
if (
|
|
8288
|
-
const aside =
|
|
8365
|
+
const folder = dirname5(library);
|
|
8366
|
+
const copy = `~${basename4(library)}`;
|
|
8367
|
+
if (existsSync6(join6(folder, copy))) {
|
|
8368
|
+
const aside = join6(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
8289
8369
|
try {
|
|
8290
|
-
renameSync(
|
|
8370
|
+
renameSync(join6(folder, copy), aside);
|
|
8291
8371
|
setAside.push(aside);
|
|
8292
8372
|
} catch {}
|
|
8293
8373
|
}
|
|
8294
8374
|
let names;
|
|
8295
8375
|
try {
|
|
8296
|
-
names =
|
|
8376
|
+
names = readdirSync4(folder);
|
|
8297
8377
|
} catch {
|
|
8298
8378
|
continue;
|
|
8299
8379
|
}
|
|
8300
8380
|
for (const name of names) {
|
|
8301
8381
|
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
8302
8382
|
try {
|
|
8303
|
-
unlinkSync(
|
|
8304
|
-
swept.push(
|
|
8383
|
+
unlinkSync(join6(folder, name));
|
|
8384
|
+
swept.push(join6(folder, name));
|
|
8305
8385
|
} catch {}
|
|
8306
8386
|
}
|
|
8307
8387
|
}
|
|
@@ -8309,7 +8389,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
8309
8389
|
return { setAside, swept };
|
|
8310
8390
|
}
|
|
8311
8391
|
function windowsLibraries(projectPath) {
|
|
8312
|
-
const listed = readOrNull(
|
|
8392
|
+
const listed = readOrNull(join6(projectPath, ".godot", "extension_list.cfg"));
|
|
8313
8393
|
if (listed === null) {
|
|
8314
8394
|
return [];
|
|
8315
8395
|
}
|
|
@@ -8328,7 +8408,7 @@ function windowsLibraries(projectPath) {
|
|
|
8328
8408
|
continue;
|
|
8329
8409
|
}
|
|
8330
8410
|
const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
|
|
8331
|
-
const library = named === undefined ? null : resolveResource(projectPath,
|
|
8411
|
+
const library = named === undefined ? null : resolveResource(projectPath, dirname5(manifest), named);
|
|
8332
8412
|
if (library !== null) {
|
|
8333
8413
|
libraries.add(library);
|
|
8334
8414
|
}
|
|
@@ -8341,9 +8421,9 @@ function resolveResource(projectPath, relativeTo, path) {
|
|
|
8341
8421
|
return null;
|
|
8342
8422
|
}
|
|
8343
8423
|
if (path.startsWith("res://")) {
|
|
8344
|
-
return
|
|
8424
|
+
return join6(projectPath, path.slice("res://".length));
|
|
8345
8425
|
}
|
|
8346
|
-
return
|
|
8426
|
+
return isAbsolute3(path) ? path : join6(relativeTo, path);
|
|
8347
8427
|
}
|
|
8348
8428
|
function readOrNull(path) {
|
|
8349
8429
|
try {
|
|
@@ -8356,33 +8436,33 @@ var init_library_copies = () => {};
|
|
|
8356
8436
|
|
|
8357
8437
|
// src/reimport.ts
|
|
8358
8438
|
import { createHash } from "node:crypto";
|
|
8359
|
-
import { existsSync as
|
|
8360
|
-
import { basename as
|
|
8439
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync5, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8440
|
+
import { basename as basename5, join as join7 } from "node:path";
|
|
8361
8441
|
function forceNextImport(projectPath, resourcePath) {
|
|
8362
8442
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
8363
|
-
const sidecar =
|
|
8364
|
-
if (!
|
|
8443
|
+
const sidecar = join7(projectPath, `${relative}.import`);
|
|
8444
|
+
if (!existsSync7(sidecar)) {
|
|
8365
8445
|
return;
|
|
8366
8446
|
}
|
|
8367
|
-
const { atime, mtimeMs } =
|
|
8447
|
+
const { atime, mtimeMs } = statSync5(sidecar);
|
|
8368
8448
|
writeFileSync3(sidecar, readFileSync4(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
8369
8449
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
8370
8450
|
rmSync3(importRecord(projectPath, resourcePath), { force: true });
|
|
8371
8451
|
}
|
|
8372
8452
|
function importRecord(projectPath, resourcePath) {
|
|
8373
8453
|
const digest = createHash("md5").update(resourcePath).digest("hex");
|
|
8374
|
-
return
|
|
8454
|
+
return join7(projectPath, ".godot", "imported", `${basename5(resourcePath)}-${digest}.md5`);
|
|
8375
8455
|
}
|
|
8376
8456
|
var init_reimport = () => {};
|
|
8377
8457
|
|
|
8378
8458
|
// src/alive.ts
|
|
8379
|
-
import
|
|
8459
|
+
import process6 from "node:process";
|
|
8380
8460
|
function alive(pid) {
|
|
8381
8461
|
if (pid === undefined || pid === null) {
|
|
8382
8462
|
return false;
|
|
8383
8463
|
}
|
|
8384
8464
|
try {
|
|
8385
|
-
|
|
8465
|
+
process6.kill(pid, 0);
|
|
8386
8466
|
return true;
|
|
8387
8467
|
} catch (error) {
|
|
8388
8468
|
return error.code === "EPERM";
|
|
@@ -8391,17 +8471,17 @@ function alive(pid) {
|
|
|
8391
8471
|
var init_alive = () => {};
|
|
8392
8472
|
|
|
8393
8473
|
// src/scratch.ts
|
|
8394
|
-
import { existsSync as
|
|
8474
|
+
import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync4 } from "node:fs";
|
|
8395
8475
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8396
|
-
import { join as
|
|
8397
|
-
import
|
|
8476
|
+
import { join as join8 } from "node:path";
|
|
8477
|
+
import process7 from "node:process";
|
|
8398
8478
|
function scratchDirectory(kind) {
|
|
8399
|
-
return mkdtempSync(
|
|
8479
|
+
return mkdtempSync(join8(tmpdir2(), `gdharness-${kind}-${process7.pid}-`));
|
|
8400
8480
|
}
|
|
8401
8481
|
function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
8402
8482
|
let entries;
|
|
8403
8483
|
try {
|
|
8404
|
-
entries =
|
|
8484
|
+
entries = readdirSync5(directory);
|
|
8405
8485
|
} catch {
|
|
8406
8486
|
return;
|
|
8407
8487
|
}
|
|
@@ -8412,7 +8492,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
|
8412
8492
|
continue;
|
|
8413
8493
|
}
|
|
8414
8494
|
try {
|
|
8415
|
-
remove(
|
|
8495
|
+
remove(join8(directory, entry));
|
|
8416
8496
|
} catch {}
|
|
8417
8497
|
}
|
|
8418
8498
|
}
|
|
@@ -8450,7 +8530,7 @@ function tryAgain(path, attempt) {
|
|
|
8450
8530
|
lastTryOnExit();
|
|
8451
8531
|
}
|
|
8452
8532
|
const after = AGAIN_AFTER_MS[attempt];
|
|
8453
|
-
if (after === undefined || !
|
|
8533
|
+
if (after === undefined || !existsSync8(path)) {
|
|
8454
8534
|
waiting.delete(path);
|
|
8455
8535
|
if (after === undefined) {
|
|
8456
8536
|
atExit.add(path);
|
|
@@ -8471,7 +8551,7 @@ function lastTryOnExit() {
|
|
|
8471
8551
|
return;
|
|
8472
8552
|
}
|
|
8473
8553
|
lastTryWatched = true;
|
|
8474
|
-
|
|
8554
|
+
process7.once("exit", () => {
|
|
8475
8555
|
for (const path of [...waiting, ...atExit]) {
|
|
8476
8556
|
try {
|
|
8477
8557
|
remove(path);
|
|
@@ -8487,6 +8567,7 @@ var init_scratch = __esm(() => {
|
|
|
8487
8567
|
waiting = new Set;
|
|
8488
8568
|
atExit = new Set;
|
|
8489
8569
|
KINDS = [
|
|
8570
|
+
"answers",
|
|
8490
8571
|
"desktop",
|
|
8491
8572
|
"export",
|
|
8492
8573
|
"import",
|
|
@@ -8497,8 +8578,8 @@ var init_scratch = __esm(() => {
|
|
|
8497
8578
|
});
|
|
8498
8579
|
|
|
8499
8580
|
// src/headless.ts
|
|
8500
|
-
import { existsSync as
|
|
8501
|
-
import { dirname as
|
|
8581
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8582
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
8502
8583
|
function snakeCased(params) {
|
|
8503
8584
|
const result = emptyRecord();
|
|
8504
8585
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -8509,8 +8590,14 @@ function snakeCased(params) {
|
|
|
8509
8590
|
}
|
|
8510
8591
|
function engineExtras(outcome) {
|
|
8511
8592
|
const after = outcome.afterAnswerMessages ?? [];
|
|
8593
|
+
const before = outcome.beforeOperationMessages ?? [];
|
|
8512
8594
|
return {
|
|
8513
8595
|
...outcome.messages.length > 0 ? { engine_messages: outcome.messages } : {},
|
|
8596
|
+
...before.length > 0 ? {
|
|
8597
|
+
engine_messages_before_operation: before,
|
|
8598
|
+
engine_messages_before_operation_note: BEFORE_OPERATION_NOTE
|
|
8599
|
+
} : {},
|
|
8600
|
+
...outcome.beforeOperationOmitted === undefined ? {} : { engine_messages_before_operation_omitted: outcome.beforeOperationOmitted },
|
|
8514
8601
|
...outcome.messagesOmitted === undefined ? {} : { engine_messages_omitted: outcome.messagesOmitted },
|
|
8515
8602
|
...after.length > 0 ? { engine_messages_after_answer: after, engine_messages_after_answer_note: AFTER_ANSWER_NOTE } : {},
|
|
8516
8603
|
...outcome.afterAnswerOmitted === undefined ? {} : { engine_messages_after_answer_omitted: outcome.afterAnswerOmitted },
|
|
@@ -8553,7 +8640,7 @@ function refusalsIn(log) {
|
|
|
8553
8640
|
}
|
|
8554
8641
|
function scriptFault(problems, script) {
|
|
8555
8642
|
const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
|
|
8556
|
-
const directories = [...new Set([spelled(
|
|
8643
|
+
const directories = [...new Set([spelled(dirname6(script)), spelled(resolvedOr(dirname6(script)))])];
|
|
8557
8644
|
return problems.find((entry) => {
|
|
8558
8645
|
const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
|
|
8559
8646
|
return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
|
|
@@ -8591,26 +8678,33 @@ async function runImport(godotPath, projectPath, options = {}) {
|
|
|
8591
8678
|
};
|
|
8592
8679
|
const first = await pass();
|
|
8593
8680
|
const collided = librariesNotCopied(first.messages);
|
|
8594
|
-
|
|
8681
|
+
const crash = first.docCacheNote;
|
|
8682
|
+
if (collided.length === 0 && crash === undefined) {
|
|
8595
8683
|
return first;
|
|
8596
8684
|
}
|
|
8597
|
-
const
|
|
8685
|
+
const { docCacheNote: _, ...again } = await pass();
|
|
8686
|
+
const second = again.ok || crash === undefined ? again : { ...again, message: `${again.message.replace(/[.\s]+$/, "")}. ${crash}` };
|
|
8598
8687
|
const still = librariesNotCopied(second.messages);
|
|
8599
8688
|
return {
|
|
8600
8689
|
...second,
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8690
|
+
...collided.length === 0 ? {} : {
|
|
8691
|
+
librariesRetried: collided,
|
|
8692
|
+
...still.length === 0 ? {} : { librariesNotLoaded: still },
|
|
8693
|
+
extensionNote: extensionRetryNote(collided, still)
|
|
8694
|
+
},
|
|
8695
|
+
...crash === undefined ? {} : { docCacheNote: crash }
|
|
8604
8696
|
};
|
|
8605
8697
|
}
|
|
8606
8698
|
async function importOnce(godotPath, projectPath) {
|
|
8607
8699
|
const logDir = scratchDirectory("import");
|
|
8700
|
+
const caches = editorCacheDirectory(godotPath);
|
|
8701
|
+
const cachesBefore = docCachesWritten(caches);
|
|
8608
8702
|
let ran;
|
|
8609
8703
|
try {
|
|
8610
8704
|
ran = await runEngine(godotPath, [
|
|
8611
8705
|
"--headless",
|
|
8612
8706
|
"--log-file",
|
|
8613
|
-
|
|
8707
|
+
join9(logDir, "engine.log"),
|
|
8614
8708
|
"--path",
|
|
8615
8709
|
projectPath,
|
|
8616
8710
|
"--import"
|
|
@@ -8619,24 +8713,26 @@ async function importOnce(godotPath, projectPath) {
|
|
|
8619
8713
|
discard(logDir);
|
|
8620
8714
|
}
|
|
8621
8715
|
const problems = problemsIn(ran.log);
|
|
8716
|
+
const crash = docCacheCrash(ran, cachesBefore, docCachesWritten(caches), "import");
|
|
8622
8717
|
if (ran.exitCode === 0 && ran.failure === null) {
|
|
8623
|
-
return { ok: true, ...reported2(problems) };
|
|
8718
|
+
return { ok: true, ...reported2(problems), ...crash === null ? {} : { docCacheNote: crash } };
|
|
8624
8719
|
}
|
|
8625
8720
|
return {
|
|
8626
8721
|
ok: false,
|
|
8627
8722
|
message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
|
|
8628
|
-
...reported2(problems)
|
|
8723
|
+
...reported2(problems),
|
|
8724
|
+
...crash === null ? {} : { docCacheNote: crash }
|
|
8629
8725
|
};
|
|
8630
8726
|
}
|
|
8631
8727
|
async function runOperation(engine, operation, params, projectPath) {
|
|
8632
8728
|
const paramsDir = scratchDirectory("params");
|
|
8633
|
-
const paramsFile =
|
|
8634
|
-
const answerFile =
|
|
8729
|
+
const paramsFile = join9(paramsDir, `${operation}.json`);
|
|
8730
|
+
const answerFile = join9(paramsDir, "answer.json");
|
|
8635
8731
|
writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
8636
8732
|
const args = [
|
|
8637
8733
|
"--headless",
|
|
8638
8734
|
"--log-file",
|
|
8639
|
-
|
|
8735
|
+
join9(paramsDir, "engine.log"),
|
|
8640
8736
|
"--path",
|
|
8641
8737
|
projectPath,
|
|
8642
8738
|
"--script",
|
|
@@ -8655,12 +8751,22 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
8655
8751
|
discard(paramsDir);
|
|
8656
8752
|
}
|
|
8657
8753
|
const everything = ran.log.everything();
|
|
8658
|
-
const
|
|
8659
|
-
const
|
|
8754
|
+
const marked = (text) => everything.findIndex((entry) => entry.source === "stderr" && entry.text === text);
|
|
8755
|
+
const answered = marked(ANSWERED);
|
|
8756
|
+
const waiting = marked(WAITING);
|
|
8757
|
+
const begun = marked(BEGUN);
|
|
8758
|
+
const ended = answered === -1 ? everything.length : answered;
|
|
8759
|
+
const problems = waiting === -1 ? problemsAmong(everything.slice(0, ended)) : problemsAmong([
|
|
8760
|
+
...everything.slice(0, waiting),
|
|
8761
|
+
...begun === -1 ? [] : everything.slice(begun + 1, ended)
|
|
8762
|
+
]);
|
|
8763
|
+
const startup = waiting === -1 ? [] : problemsAmong(everything.slice(waiting + 1, begun === -1 ? ended : begun));
|
|
8660
8764
|
const after = answered === -1 ? [] : problemsAmong(everything.slice(answered + 1));
|
|
8661
8765
|
const afterAnswered = {
|
|
8662
8766
|
...after.length === 0 ? {} : { afterAnswerMessages: after.slice(-MESSAGES_KEPT) },
|
|
8663
|
-
...after.length > MESSAGES_KEPT ? { afterAnswerOmitted: after.length - MESSAGES_KEPT } : {}
|
|
8767
|
+
...after.length > MESSAGES_KEPT ? { afterAnswerOmitted: after.length - MESSAGES_KEPT } : {},
|
|
8768
|
+
...startup.length === 0 ? {} : { beforeOperationMessages: startup.slice(-MESSAGES_KEPT) },
|
|
8769
|
+
...startup.length > MESSAGES_KEPT ? { beforeOperationOmitted: startup.length - MESSAGES_KEPT } : {}
|
|
8664
8770
|
};
|
|
8665
8771
|
if (answer.kind === "unreadable") {
|
|
8666
8772
|
return {
|
|
@@ -8671,6 +8777,14 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
8671
8777
|
};
|
|
8672
8778
|
}
|
|
8673
8779
|
if (answer.kind === "none") {
|
|
8780
|
+
if (waiting !== -1 && begun === -1) {
|
|
8781
|
+
return {
|
|
8782
|
+
ok: false,
|
|
8783
|
+
message: `${operation} never ran: the engine ended (${howItEnded(ran)}) while it started the project's ` + `autoloads, which this operation waits for. The last it reported: ${reason(ran.log, startup)}`,
|
|
8784
|
+
...reported2(problems),
|
|
8785
|
+
...afterAnswered
|
|
8786
|
+
};
|
|
8787
|
+
}
|
|
8674
8788
|
const refusals = refusalsIn(ran.log);
|
|
8675
8789
|
if (refusals.length > 0) {
|
|
8676
8790
|
const said = new Set(refusals.map((entry) => entry.index));
|
|
@@ -8706,7 +8820,7 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
8706
8820
|
};
|
|
8707
8821
|
}
|
|
8708
8822
|
function answerIn(file) {
|
|
8709
|
-
if (!
|
|
8823
|
+
if (!existsSync9(file)) {
|
|
8710
8824
|
return { kind: "none" };
|
|
8711
8825
|
}
|
|
8712
8826
|
const text = readFileSync5(file, "utf8");
|
|
@@ -8720,8 +8834,9 @@ function answerIn(file) {
|
|
|
8720
8834
|
};
|
|
8721
8835
|
}
|
|
8722
8836
|
}
|
|
8723
|
-
var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
|
|
8837
|
+
var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", WAITING = "[gdharness] waiting for the autoloads", BEGUN = "[gdharness] begun", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
|
|
8724
8838
|
var init_headless = __esm(() => {
|
|
8839
|
+
init_doc_cache();
|
|
8725
8840
|
init_engine_run();
|
|
8726
8841
|
init_library_copies();
|
|
8727
8842
|
init_reimport();
|
|
@@ -8729,9 +8844,9 @@ var init_headless = __esm(() => {
|
|
|
8729
8844
|
});
|
|
8730
8845
|
|
|
8731
8846
|
// src/update-check.ts
|
|
8732
|
-
import { existsSync as
|
|
8733
|
-
import { homedir as
|
|
8734
|
-
import { join as
|
|
8847
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8848
|
+
import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
|
|
8849
|
+
import { join as join10 } from "node:path";
|
|
8735
8850
|
function packageUrl(root) {
|
|
8736
8851
|
return `${root.replace(/\/+$/, "")}/gdharness/latest`;
|
|
8737
8852
|
}
|
|
@@ -8751,27 +8866,27 @@ function cacheDirectory(environment) {
|
|
|
8751
8866
|
const value = environment[name];
|
|
8752
8867
|
return value !== undefined && value !== "" ? value : null;
|
|
8753
8868
|
};
|
|
8754
|
-
const home = set("HOME") ??
|
|
8869
|
+
const home = set("HOME") ?? homedir4();
|
|
8755
8870
|
if (process.platform === "win32") {
|
|
8756
|
-
return
|
|
8871
|
+
return join10(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
8757
8872
|
}
|
|
8758
8873
|
if (process.platform === "darwin") {
|
|
8759
|
-
return
|
|
8874
|
+
return join10(home, "Library", "Caches", "gdharness");
|
|
8760
8875
|
}
|
|
8761
|
-
return
|
|
8876
|
+
return join10(set("XDG_CACHE_HOME") ?? join10(home, ".cache"), "gdharness");
|
|
8762
8877
|
}
|
|
8763
8878
|
function cacheFile(environment = process.env) {
|
|
8764
8879
|
try {
|
|
8765
8880
|
const directory = cacheDirectory(environment);
|
|
8766
8881
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
8767
|
-
return
|
|
8882
|
+
return join10(directory, "update-check.json");
|
|
8768
8883
|
} catch {
|
|
8769
|
-
return
|
|
8884
|
+
return join10(tmpdir3(), "gdharness-update-check.json");
|
|
8770
8885
|
}
|
|
8771
8886
|
}
|
|
8772
8887
|
function readCache(path) {
|
|
8773
8888
|
try {
|
|
8774
|
-
if (!
|
|
8889
|
+
if (!existsSync10(path)) {
|
|
8775
8890
|
return null;
|
|
8776
8891
|
}
|
|
8777
8892
|
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
@@ -9006,7 +9121,7 @@ var init_server_version = __esm(() => {
|
|
|
9006
9121
|
|
|
9007
9122
|
// src/issues.ts
|
|
9008
9123
|
import { createHash as createHash2 } from "node:crypto";
|
|
9009
|
-
import
|
|
9124
|
+
import process8 from "node:process";
|
|
9010
9125
|
function feedbackNotice(every) {
|
|
9011
9126
|
return JSON.stringify({
|
|
9012
9127
|
gdharness_feedback: {
|
|
@@ -9017,9 +9132,9 @@ function feedbackNotice(every) {
|
|
|
9017
9132
|
}, null, 2);
|
|
9018
9133
|
}
|
|
9019
9134
|
function runtime() {
|
|
9020
|
-
const versions =
|
|
9135
|
+
const versions = process8.versions;
|
|
9021
9136
|
const bun = versions["bun"];
|
|
9022
|
-
return bun === undefined ? `node ${
|
|
9137
|
+
return bun === undefined ? `node ${process8.versions.node}` : `bun ${bun}`;
|
|
9023
9138
|
}
|
|
9024
9139
|
function defectSignature(where, message) {
|
|
9025
9140
|
const normalised = `${where}
|
|
@@ -9031,7 +9146,7 @@ function facts(where, message, godotVersion) {
|
|
|
9031
9146
|
["Where", where],
|
|
9032
9147
|
["Error", message],
|
|
9033
9148
|
["Version", `gdharness ${SERVER_VERSION}`],
|
|
9034
|
-
["Runtime", `${runtime()}, ${
|
|
9149
|
+
["Runtime", `${runtime()}, ${process8.platform} ${process8.arch}`],
|
|
9035
9150
|
["Godot", godotVersion ?? "not known at the point this failed"],
|
|
9036
9151
|
["Signature", defectSignature(where, message)]
|
|
9037
9152
|
];
|
|
@@ -9041,7 +9156,7 @@ function filledTemplate(where, message, godotVersion) {
|
|
|
9041
9156
|
`**gdharness version**: ${SERVER_VERSION}`,
|
|
9042
9157
|
`**Godot version**: ${godotVersion ?? ""}`,
|
|
9043
9158
|
`**Bun version**: ${runtime()}`,
|
|
9044
|
-
`**OS**: ${
|
|
9159
|
+
`**OS**: ${process8.platform} ${process8.arch}`,
|
|
9045
9160
|
"**MCP client**:",
|
|
9046
9161
|
"",
|
|
9047
9162
|
"## What you did",
|
|
@@ -9781,8 +9896,8 @@ var init_gdscript_source = __esm(() => {
|
|
|
9781
9896
|
});
|
|
9782
9897
|
|
|
9783
9898
|
// src/class-cache.ts
|
|
9784
|
-
import { existsSync as
|
|
9785
|
-
import { join as
|
|
9899
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "node:fs";
|
|
9900
|
+
import { join as join11, posix } from "node:path";
|
|
9786
9901
|
function isToolScript(source) {
|
|
9787
9902
|
return TOOL.test(codeOf(source));
|
|
9788
9903
|
}
|
|
@@ -9798,14 +9913,14 @@ function declaredClasses(projectPath) {
|
|
|
9798
9913
|
}
|
|
9799
9914
|
function eachScript(projectPath, each) {
|
|
9800
9915
|
const visit = (directory, prefix) => {
|
|
9801
|
-
if (
|
|
9916
|
+
if (existsSync11(join11(directory, ".gdignore"))) {
|
|
9802
9917
|
return;
|
|
9803
9918
|
}
|
|
9804
|
-
for (const entry of
|
|
9919
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
9805
9920
|
if (entry.name.startsWith(".")) {
|
|
9806
9921
|
continue;
|
|
9807
9922
|
}
|
|
9808
|
-
const path =
|
|
9923
|
+
const path = join11(directory, entry.name);
|
|
9809
9924
|
if (entry.isDirectory()) {
|
|
9810
9925
|
visit(path, `${prefix}${entry.name}/`);
|
|
9811
9926
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
@@ -9831,8 +9946,8 @@ function scriptsNaming(projectPath, classes) {
|
|
|
9831
9946
|
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
9832
9947
|
}
|
|
9833
9948
|
function cachedClasses(projectPath) {
|
|
9834
|
-
const cache =
|
|
9835
|
-
if (!
|
|
9949
|
+
const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
9950
|
+
if (!existsSync11(cache)) {
|
|
9836
9951
|
return null;
|
|
9837
9952
|
}
|
|
9838
9953
|
const listed = new Map;
|
|
@@ -10102,9 +10217,9 @@ function uncachedClassNote(types) {
|
|
|
10102
10217
|
return `${types.join(", ")} ${one ? "is declared" : "are declared"} in this project and missing from ` + `.godot/global_script_class_cache.cfg, so a game launched now would not resolve ${one ? "it" : "them"} ` + "either. Try editor_rescan first: it asks the editor already running and starts nothing, and it " + "cleared this in 314ms in a second project with a 31-worker fan-out still importing. It is not " + "always enough, because a file another engine has already imported reads as settled and the " + "walk does not look inside it. project_import refresh_classes rewrites the cache from the " + "declarations on disk and does reach that case, at the cost of a short headless engine, which " + "is worth knowing when something else is already running against this project.";
|
|
10103
10218
|
}
|
|
10104
10219
|
function cacheWrittenAt(projectPath) {
|
|
10105
|
-
const cache =
|
|
10220
|
+
const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
10106
10221
|
try {
|
|
10107
|
-
return
|
|
10222
|
+
return statSync6(cache).mtimeMs;
|
|
10108
10223
|
} catch {
|
|
10109
10224
|
return null;
|
|
10110
10225
|
}
|
|
@@ -10116,14 +10231,14 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
10116
10231
|
return false;
|
|
10117
10232
|
}
|
|
10118
10233
|
try {
|
|
10119
|
-
return
|
|
10234
|
+
return statSync6(join11(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
10120
10235
|
} catch {
|
|
10121
10236
|
return false;
|
|
10122
10237
|
}
|
|
10123
10238
|
});
|
|
10124
10239
|
}
|
|
10125
10240
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
10126
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !
|
|
10241
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync11(join11(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
10127
10242
|
}
|
|
10128
10243
|
function staleAgainst(cached, projectPath) {
|
|
10129
10244
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -10148,8 +10263,8 @@ var init_class_cache = __esm(() => {
|
|
|
10148
10263
|
});
|
|
10149
10264
|
|
|
10150
10265
|
// src/project-scan.ts
|
|
10151
|
-
import { existsSync as
|
|
10152
|
-
import { extname, join as
|
|
10266
|
+
import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "node:fs";
|
|
10267
|
+
import { extname, join as join12 } from "node:path";
|
|
10153
10268
|
function skipped(name) {
|
|
10154
10269
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
10155
10270
|
}
|
|
@@ -10157,10 +10272,10 @@ function steppedOver(directory) {
|
|
|
10157
10272
|
return gdignored(directory) || installedByUs(directory);
|
|
10158
10273
|
}
|
|
10159
10274
|
function gdignored(directory) {
|
|
10160
|
-
return
|
|
10275
|
+
return existsSync12(join12(directory, ".gdignore"));
|
|
10161
10276
|
}
|
|
10162
10277
|
function installedByUs(directory) {
|
|
10163
|
-
return
|
|
10278
|
+
return existsSync12(join12(directory, VERSION_MARKER));
|
|
10164
10279
|
}
|
|
10165
10280
|
function projectStructure(projectPath) {
|
|
10166
10281
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -10168,12 +10283,12 @@ function projectStructure(projectPath) {
|
|
|
10168
10283
|
if (steppedOver(directory)) {
|
|
10169
10284
|
return;
|
|
10170
10285
|
}
|
|
10171
|
-
for (const entry of
|
|
10286
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10172
10287
|
if (skipped(entry.name)) {
|
|
10173
10288
|
continue;
|
|
10174
10289
|
}
|
|
10175
10290
|
if (entry.isDirectory()) {
|
|
10176
|
-
visit(
|
|
10291
|
+
visit(join12(directory, entry.name));
|
|
10177
10292
|
} else if (entry.isFile()) {
|
|
10178
10293
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
10179
10294
|
if (SCENE_EXTENSIONS.has(extension)) {
|
|
@@ -10197,13 +10312,13 @@ function projectFiles(projectPath, extensions) {
|
|
|
10197
10312
|
if (steppedOver(directory)) {
|
|
10198
10313
|
return;
|
|
10199
10314
|
}
|
|
10200
|
-
for (const entry of
|
|
10315
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10201
10316
|
if (skipped(entry.name)) {
|
|
10202
10317
|
continue;
|
|
10203
10318
|
}
|
|
10204
10319
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
10205
10320
|
if (entry.isDirectory()) {
|
|
10206
|
-
visit(
|
|
10321
|
+
visit(join12(directory, entry.name), spelled);
|
|
10207
10322
|
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
10208
10323
|
found.push(spelled);
|
|
10209
10324
|
}
|
|
@@ -10218,20 +10333,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
10218
10333
|
if (steppedOver(directory)) {
|
|
10219
10334
|
return;
|
|
10220
10335
|
}
|
|
10221
|
-
for (const entry of
|
|
10336
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10222
10337
|
if (skipped(entry.name)) {
|
|
10223
10338
|
continue;
|
|
10224
10339
|
}
|
|
10225
10340
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
10226
10341
|
if (entry.isDirectory()) {
|
|
10227
|
-
visit(
|
|
10342
|
+
visit(join12(directory, entry.name), spelled);
|
|
10228
10343
|
continue;
|
|
10229
10344
|
}
|
|
10230
10345
|
if (!entry.isFile()) {
|
|
10231
10346
|
continue;
|
|
10232
10347
|
}
|
|
10233
10348
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
10234
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
10349
|
+
if (CARRIES_A_UID.has(extension) && !existsSync12(join12(directory, `${entry.name}.uid`))) {
|
|
10235
10350
|
found.push(spelled);
|
|
10236
10351
|
}
|
|
10237
10352
|
}
|
|
@@ -10248,11 +10363,11 @@ function projectTexts(projectPath) {
|
|
|
10248
10363
|
if (steppedOver(directory)) {
|
|
10249
10364
|
return;
|
|
10250
10365
|
}
|
|
10251
|
-
for (const entry of
|
|
10366
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10252
10367
|
if (skipped(entry.name)) {
|
|
10253
10368
|
continue;
|
|
10254
10369
|
}
|
|
10255
|
-
const absolute =
|
|
10370
|
+
const absolute = join12(directory, entry.name);
|
|
10256
10371
|
const spelled = `${prefix}${entry.name}`;
|
|
10257
10372
|
if (entry.isDirectory()) {
|
|
10258
10373
|
visit(absolute, `${spelled}/`);
|
|
@@ -10278,11 +10393,11 @@ function ignoredTexts(projectPath) {
|
|
|
10278
10393
|
return;
|
|
10279
10394
|
}
|
|
10280
10395
|
const under = ignored || gdignored(directory);
|
|
10281
|
-
for (const entry of
|
|
10396
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10282
10397
|
if (skipped(entry.name)) {
|
|
10283
10398
|
continue;
|
|
10284
10399
|
}
|
|
10285
|
-
const absolute =
|
|
10400
|
+
const absolute = join12(directory, entry.name);
|
|
10286
10401
|
const spelled = `${prefix}${entry.name}`;
|
|
10287
10402
|
if (entry.isDirectory()) {
|
|
10288
10403
|
visit(absolute, `${spelled}/`, under);
|
|
@@ -10315,14 +10430,14 @@ function searchProject(projectPath, options) {
|
|
|
10315
10430
|
if (steppedOver(directory)) {
|
|
10316
10431
|
return;
|
|
10317
10432
|
}
|
|
10318
|
-
for (const entry of
|
|
10433
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10319
10434
|
if (full()) {
|
|
10320
10435
|
return;
|
|
10321
10436
|
}
|
|
10322
10437
|
if (skipped(entry.name)) {
|
|
10323
10438
|
continue;
|
|
10324
10439
|
}
|
|
10325
|
-
const entryPath =
|
|
10440
|
+
const entryPath = join12(directory, entry.name);
|
|
10326
10441
|
if (entry.isDirectory()) {
|
|
10327
10442
|
visit(entryPath);
|
|
10328
10443
|
continue;
|
|
@@ -10767,16 +10882,16 @@ function readParams(params, key) {
|
|
|
10767
10882
|
import { createHash as createHash3 } from "node:crypto";
|
|
10768
10883
|
import {
|
|
10769
10884
|
cpSync,
|
|
10770
|
-
existsSync as
|
|
10885
|
+
existsSync as existsSync13,
|
|
10771
10886
|
mkdirSync as mkdirSync4,
|
|
10772
|
-
readdirSync as
|
|
10887
|
+
readdirSync as readdirSync8,
|
|
10773
10888
|
readFileSync as readFileSync11,
|
|
10774
10889
|
rmdirSync,
|
|
10775
10890
|
rmSync as rmSync5,
|
|
10776
|
-
statSync as
|
|
10891
|
+
statSync as statSync7,
|
|
10777
10892
|
writeFileSync as writeFileSync6
|
|
10778
10893
|
} from "node:fs";
|
|
10779
|
-
import { dirname as
|
|
10894
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
10780
10895
|
import { fileURLToPath } from "node:url";
|
|
10781
10896
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
10782
10897
|
return addonsDigest(EDITOR_PLUGINS, from);
|
|
@@ -10787,13 +10902,13 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
10787
10902
|
function addonsDigest(names, from) {
|
|
10788
10903
|
const hash = createHash3("sha256");
|
|
10789
10904
|
const walk = (directory, relative) => {
|
|
10790
|
-
for (const name of
|
|
10905
|
+
for (const name of readdirSync8(directory).sort()) {
|
|
10791
10906
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
10792
10907
|
continue;
|
|
10793
10908
|
}
|
|
10794
|
-
const path =
|
|
10909
|
+
const path = join13(directory, name);
|
|
10795
10910
|
const inside = `${relative}/${name}`;
|
|
10796
|
-
if (
|
|
10911
|
+
if (statSync7(path).isDirectory()) {
|
|
10797
10912
|
walk(path, inside);
|
|
10798
10913
|
} else {
|
|
10799
10914
|
hash.update(`${inside}
|
|
@@ -10805,7 +10920,7 @@ function addonsDigest(names, from) {
|
|
|
10805
10920
|
}
|
|
10806
10921
|
};
|
|
10807
10922
|
for (const name of names) {
|
|
10808
|
-
walk(
|
|
10923
|
+
walk(join13(from, name), name);
|
|
10809
10924
|
}
|
|
10810
10925
|
return hash.digest("hex");
|
|
10811
10926
|
}
|
|
@@ -10836,44 +10951,44 @@ function installedRuntimeDigest(projectPath) {
|
|
|
10836
10951
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
10837
10952
|
}
|
|
10838
10953
|
function digestMarker(projectPath, addon) {
|
|
10839
|
-
const marker =
|
|
10840
|
-
if (!
|
|
10954
|
+
const marker = join13(projectPath, "addons", addon, DIGEST_MARKER);
|
|
10955
|
+
if (!existsSync13(marker)) {
|
|
10841
10956
|
return null;
|
|
10842
10957
|
}
|
|
10843
10958
|
const said = readFileSync11(marker, "utf8").trim();
|
|
10844
10959
|
return said === "" ? null : said;
|
|
10845
10960
|
}
|
|
10846
10961
|
function shippedAddonsDirectory() {
|
|
10847
|
-
return
|
|
10962
|
+
return join13(dirname7(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
10848
10963
|
}
|
|
10849
10964
|
function shippedOperationsScript() {
|
|
10850
|
-
return
|
|
10965
|
+
return join13(dirname7(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
10851
10966
|
}
|
|
10852
10967
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
10853
10968
|
const installed = [];
|
|
10854
10969
|
for (const name of ADDONS) {
|
|
10855
|
-
const source =
|
|
10856
|
-
if (!
|
|
10970
|
+
const source = join13(from, name);
|
|
10971
|
+
if (!existsSync13(join13(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
10857
10972
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
10858
10973
|
}
|
|
10859
|
-
const target =
|
|
10860
|
-
const replaced =
|
|
10974
|
+
const target = join13(projectPath, "addons", name);
|
|
10975
|
+
const replaced = existsSync13(target);
|
|
10861
10976
|
rmSync5(target, { recursive: true, force: true });
|
|
10862
|
-
mkdirSync4(
|
|
10977
|
+
mkdirSync4(dirname7(target), { recursive: true });
|
|
10863
10978
|
cpSync(source, target, { recursive: true });
|
|
10864
|
-
writeFileSync6(
|
|
10979
|
+
writeFileSync6(join13(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
10865
10980
|
`);
|
|
10866
10981
|
installed.push({ name, path: target, replaced });
|
|
10867
10982
|
}
|
|
10868
|
-
writeFileSync6(
|
|
10983
|
+
writeFileSync6(join13(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
10869
10984
|
`);
|
|
10870
|
-
writeFileSync6(
|
|
10985
|
+
writeFileSync6(join13(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
|
|
10871
10986
|
`);
|
|
10872
10987
|
return installed;
|
|
10873
10988
|
}
|
|
10874
10989
|
function installedAddonVersion(projectPath) {
|
|
10875
|
-
const marker =
|
|
10876
|
-
if (!
|
|
10990
|
+
const marker = join13(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
10991
|
+
if (!existsSync13(marker)) {
|
|
10877
10992
|
return null;
|
|
10878
10993
|
}
|
|
10879
10994
|
const said = readFileSync11(marker, "utf8").trim();
|
|
@@ -10882,14 +10997,14 @@ function installedAddonVersion(projectPath) {
|
|
|
10882
10997
|
function removeAddons(projectPath) {
|
|
10883
10998
|
const removed = [];
|
|
10884
10999
|
for (const name of ADDONS) {
|
|
10885
|
-
const target =
|
|
10886
|
-
if (
|
|
11000
|
+
const target = join13(projectPath, "addons", name);
|
|
11001
|
+
if (existsSync13(target)) {
|
|
10887
11002
|
rmSync5(target, { recursive: true, force: true });
|
|
10888
11003
|
removed.push(target);
|
|
10889
11004
|
}
|
|
10890
11005
|
}
|
|
10891
|
-
const addons =
|
|
10892
|
-
if (removed.length > 0 &&
|
|
11006
|
+
const addons = join13(projectPath, "addons");
|
|
11007
|
+
if (removed.length > 0 && existsSync13(addons) && readdirSync8(addons).length === 0) {
|
|
10893
11008
|
rmdirSync(addons);
|
|
10894
11009
|
removed.push(addons);
|
|
10895
11010
|
}
|
|
@@ -10923,7 +11038,7 @@ function loaderAmong(projectPath, named) {
|
|
|
10923
11038
|
}
|
|
10924
11039
|
let source = null;
|
|
10925
11040
|
try {
|
|
10926
|
-
source = readFileSync11(
|
|
11041
|
+
source = readFileSync11(join13(projectPath, path), "utf8");
|
|
10927
11042
|
} catch {}
|
|
10928
11043
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
10929
11044
|
return { name: entry, path, exists: true };
|
|
@@ -10965,10 +11080,10 @@ function enabledPlugins(settings) {
|
|
|
10965
11080
|
function inspectProject(projectPath) {
|
|
10966
11081
|
const problems = [];
|
|
10967
11082
|
const addons = ADDONS.map((name) => {
|
|
10968
|
-
const directory =
|
|
10969
|
-
const marker =
|
|
10970
|
-
const installed =
|
|
10971
|
-
const version =
|
|
11083
|
+
const directory = join13(projectPath, "addons", name);
|
|
11084
|
+
const marker = join13(directory, VERSION_MARKER);
|
|
11085
|
+
const installed = existsSync13(directory);
|
|
11086
|
+
const version = existsSync13(marker) ? readFileSync11(marker, "utf8").trim() : null;
|
|
10972
11087
|
const current = version === SERVER_VERSION;
|
|
10973
11088
|
if (!installed) {
|
|
10974
11089
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -10977,7 +11092,7 @@ function inspectProject(projectPath) {
|
|
|
10977
11092
|
}
|
|
10978
11093
|
return { name, installed, version, current };
|
|
10979
11094
|
});
|
|
10980
|
-
const settings = parseProjectGodot(readFileSync11(
|
|
11095
|
+
const settings = parseProjectGodot(readFileSync11(join13(projectPath, "project.godot"), "utf8"));
|
|
10981
11096
|
const pluginsEnabled = enabledPlugins(settings);
|
|
10982
11097
|
for (const name of EDITOR_PLUGINS) {
|
|
10983
11098
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -10993,7 +11108,7 @@ function inspectProject(projectPath) {
|
|
|
10993
11108
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
10994
11109
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
10995
11110
|
for (const [name, path] of named) {
|
|
10996
|
-
if (!
|
|
11111
|
+
if (!existsSync13(join13(projectPath, path))) {
|
|
10997
11112
|
if (!addonMissing(path)) {
|
|
10998
11113
|
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`);
|
|
10999
11114
|
}
|
|
@@ -11141,7 +11256,7 @@ function annotationsOf(spec) {
|
|
|
11141
11256
|
openWorldHint: false
|
|
11142
11257
|
};
|
|
11143
11258
|
}
|
|
11144
|
-
var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR, TOOL_SPECS, SPECS_BY_NAME;
|
|
11259
|
+
var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. An editor of the project that is running and has not reached this server is waited for rather than written around.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR, TOOL_SPECS, SPECS_BY_NAME;
|
|
11145
11260
|
var init_tool_definitions = __esm(() => {
|
|
11146
11261
|
init_launch();
|
|
11147
11262
|
PROJECT_INFO_SECTIONS = [
|
|
@@ -11179,7 +11294,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11179
11294
|
};
|
|
11180
11295
|
PROPERTIES = {
|
|
11181
11296
|
type: "object",
|
|
11182
|
-
description:
|
|
11297
|
+
description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds. ${WHOLE_NUMBERS}`,
|
|
11183
11298
|
additionalProperties: true
|
|
11184
11299
|
};
|
|
11185
11300
|
XY = {
|
|
@@ -11223,7 +11338,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
11223
11338
|
name: { type: "string" },
|
|
11224
11339
|
params: { type: "string", description: 'Functions and signals: the parameter list, "delta: float".' },
|
|
11225
11340
|
returnType: { type: "string", description: "Functions: the return type." },
|
|
11226
|
-
body: {
|
|
11341
|
+
body: {
|
|
11342
|
+
type: "string",
|
|
11343
|
+
description: "Functions: the body, its lines separated by \\n. Indent shared by every line is taken off and one tab put on, so it can be written indented or not."
|
|
11344
|
+
},
|
|
11227
11345
|
varType: {
|
|
11228
11346
|
type: "string",
|
|
11229
11347
|
description: "Variables and constants: the type annotation; without one, the type the value evaluates to."
|
|
@@ -11326,7 +11444,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11326
11444
|
{
|
|
11327
11445
|
name: "project_settings",
|
|
11328
11446
|
title: "Project settings",
|
|
11329
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change
|
|
11447
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change moves no other line; the save is the engine's, and like the editor's it writes Godot's header and config/features into a file that lacks them. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
11330
11448
|
parameters: {
|
|
11331
11449
|
projectPath: PROJECT_PATH,
|
|
11332
11450
|
setting: {
|
|
@@ -11504,24 +11622,32 @@ var init_tool_definitions = __esm(() => {
|
|
|
11504
11622
|
description: "The import pipeline: what needs importing, how a resource is imported, reimports, UIDs, and the global class list the editor and the engine read.",
|
|
11505
11623
|
parameters: {
|
|
11506
11624
|
projectPath: PROJECT_PATH,
|
|
11507
|
-
resourcePath: RESOURCE_PATH,
|
|
11625
|
+
resourcePath: { ...RESOURCE_PATH, ops: ["status", "options", "set_options", "uid", "reimport"] },
|
|
11508
11626
|
includeUpToDate: {
|
|
11509
11627
|
type: "boolean",
|
|
11628
|
+
ops: ["status"],
|
|
11510
11629
|
description: "status: list resources that are current as well. Default false."
|
|
11511
11630
|
},
|
|
11512
11631
|
options: {
|
|
11513
11632
|
type: "object",
|
|
11633
|
+
ops: ["set_options"],
|
|
11514
11634
|
description: `set_options: import options keyed as the .import file spells them, {"compress/mode": 1}. A dictionary option whose keys are numbers, a font's opentype_features or any option already holding number keys, takes them written as strings of digits, and opentype_features also takes an OpenType tag by its four-letter name: {"opentype_features": {"liga": 0}} turns ligatures off. Both are written as the numbers the engine reads.`,
|
|
11515
11635
|
additionalProperties: true
|
|
11516
11636
|
},
|
|
11517
11637
|
reimport: {
|
|
11518
11638
|
type: "boolean",
|
|
11639
|
+
ops: ["set_options"],
|
|
11519
11640
|
description: "set_options: reimport afterwards, answered under reimport as project_import reimport answers. Default true."
|
|
11520
11641
|
},
|
|
11521
|
-
force: {
|
|
11642
|
+
force: {
|
|
11643
|
+
type: "boolean",
|
|
11644
|
+
ops: ["reimport"],
|
|
11645
|
+
description: "reimport: reimport even what is current. Default false."
|
|
11646
|
+
},
|
|
11522
11647
|
timeoutMs: {
|
|
11523
11648
|
type: "integer",
|
|
11524
11649
|
minimum: 1,
|
|
11650
|
+
ops: ["reimport", "set_options"],
|
|
11525
11651
|
description: "reimport, set_options: how long to wait for the editor to finish reimporting. Default 300000."
|
|
11526
11652
|
}
|
|
11527
11653
|
},
|
|
@@ -11599,7 +11725,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11599
11725
|
name: "project_test",
|
|
11600
11726
|
title: "Run tests",
|
|
11601
11727
|
effect: "additive",
|
|
11602
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one,
|
|
11728
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
11603
11729
|
parameters: {
|
|
11604
11730
|
projectPath: PROJECT_PATH,
|
|
11605
11731
|
path: {
|
|
@@ -11636,14 +11762,14 @@ var init_tool_definitions = __esm(() => {
|
|
|
11636
11762
|
{
|
|
11637
11763
|
name: "scene_create",
|
|
11638
11764
|
title: "Create or save a scene",
|
|
11639
|
-
description:
|
|
11765
|
+
description: `Creates a scene file where none is, saves one, or saves a copy under a new path. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
|
|
11640
11766
|
parameters: {
|
|
11641
11767
|
projectPath: PROJECT_PATH,
|
|
11642
11768
|
scenePath: SCENE_PATH,
|
|
11643
11769
|
rootNodeType: {
|
|
11644
11770
|
type: "string",
|
|
11645
11771
|
ops: ["create"],
|
|
11646
|
-
description: "create: the root node class. Default Node."
|
|
11772
|
+
description: "create: the root node class, an engine class or a class_name a project script declares, which the root is made as with that script attached. Default Node."
|
|
11647
11773
|
},
|
|
11648
11774
|
rootNodeName: {
|
|
11649
11775
|
type: "string",
|
|
@@ -11681,29 +11807,32 @@ var init_tool_definitions = __esm(() => {
|
|
|
11681
11807
|
{
|
|
11682
11808
|
name: "scene_node",
|
|
11683
11809
|
title: "Scene nodes",
|
|
11684
|
-
description:
|
|
11810
|
+
description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene, and for a node that belongs to a scene this one instances or inherits. ${WITHOUT_AN_EDITOR}`,
|
|
11685
11811
|
parameters: {
|
|
11686
11812
|
projectPath: PROJECT_PATH,
|
|
11687
11813
|
scenePath: SCENE_PATH,
|
|
11688
|
-
nodePath: NODE_PATH,
|
|
11814
|
+
nodePath: { ...NODE_PATH, ops: ["get", "set", "duplicate", "reparent", "delete", "set_tilemap_cells"] },
|
|
11689
11815
|
parentNodePath: {
|
|
11690
11816
|
type: "string",
|
|
11817
|
+
ops: ["add", "duplicate"],
|
|
11691
11818
|
description: "add: where the node goes. Default the root. duplicate: where the copy goes."
|
|
11692
11819
|
},
|
|
11693
11820
|
nodeType: {
|
|
11694
11821
|
type: "string",
|
|
11695
|
-
|
|
11822
|
+
ops: ["add"],
|
|
11823
|
+
description: 'add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath.'
|
|
11696
11824
|
},
|
|
11697
11825
|
instancePath: {
|
|
11698
11826
|
type: "string",
|
|
11699
11827
|
ops: ["add"],
|
|
11700
11828
|
description: `add: a scene to instance as the new node instead of a class, such as "ui/panel.tscn". It is saved as an instance of that file, the way the editor's Instantiate Child Scene saves one, so changes to that scene reach it; properties set its root. A scene that is the target or instances it is refused.`
|
|
11701
11829
|
},
|
|
11702
|
-
nodeName: { type: "string", description: "add: the new node's name." },
|
|
11703
|
-
properties: PROPERTIES,
|
|
11704
|
-
newName: { type: "string", description: "duplicate: the copy's name." },
|
|
11830
|
+
nodeName: { type: "string", ops: ["add"], description: "add: the new node's name." },
|
|
11831
|
+
properties: { ...PROPERTIES, ops: ["add", "set"] },
|
|
11832
|
+
newName: { type: "string", ops: ["duplicate"], description: "duplicate: the copy's name." },
|
|
11705
11833
|
newParentPath: {
|
|
11706
11834
|
type: "string",
|
|
11835
|
+
ops: ["reparent"],
|
|
11707
11836
|
description: "reparent: the new parent, which may be the one it has, to move it among its siblings."
|
|
11708
11837
|
},
|
|
11709
11838
|
index: {
|
|
@@ -11714,13 +11843,15 @@ var init_tool_definitions = __esm(() => {
|
|
|
11714
11843
|
},
|
|
11715
11844
|
includeDefaults: {
|
|
11716
11845
|
type: "boolean",
|
|
11846
|
+
ops: ["get"],
|
|
11717
11847
|
description: "get: include properties still at their default. Default false."
|
|
11718
11848
|
},
|
|
11719
11849
|
layer: {
|
|
11720
11850
|
type: "number",
|
|
11851
|
+
ops: ["set_tilemap_cells"],
|
|
11721
11852
|
description: "set_tilemap_cells: the layer of a TileMap. Default 0. A TileMapLayer is one layer."
|
|
11722
11853
|
},
|
|
11723
|
-
cells: TILEMAP_CELLS
|
|
11854
|
+
cells: { ...TILEMAP_CELLS, ops: ["set_tilemap_cells"] }
|
|
11724
11855
|
},
|
|
11725
11856
|
requires: ["projectPath", "scenePath"],
|
|
11726
11857
|
operations: {
|
|
@@ -11760,16 +11891,32 @@ var init_tool_definitions = __esm(() => {
|
|
|
11760
11891
|
{
|
|
11761
11892
|
name: "scene_signal",
|
|
11762
11893
|
title: "Scene signals",
|
|
11763
|
-
description:
|
|
11894
|
+
description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connect of a pair already connected says alreadyConnected, with flagsBefore when it changed the flags. ${WITHOUT_AN_EDITOR}`,
|
|
11764
11895
|
parameters: {
|
|
11765
11896
|
projectPath: PROJECT_PATH,
|
|
11766
11897
|
scenePath: SCENE_PATH,
|
|
11767
|
-
sourceNodePath: { type: "string", description: "The node that emits." },
|
|
11768
|
-
signalName: {
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11898
|
+
sourceNodePath: { type: "string", ops: ["connect", "disconnect"], description: "The node that emits." },
|
|
11899
|
+
signalName: {
|
|
11900
|
+
type: "string",
|
|
11901
|
+
ops: ["connect", "disconnect"],
|
|
11902
|
+
description: 'The signal it emits, such as "pressed".'
|
|
11903
|
+
},
|
|
11904
|
+
targetNodePath: {
|
|
11905
|
+
type: "string",
|
|
11906
|
+
ops: ["connect", "disconnect"],
|
|
11907
|
+
description: "The node whose method is called."
|
|
11908
|
+
},
|
|
11909
|
+
methodName: {
|
|
11910
|
+
type: "string",
|
|
11911
|
+
ops: ["connect", "disconnect"],
|
|
11912
|
+
description: "The method on that node, called when it emits."
|
|
11913
|
+
},
|
|
11914
|
+
flags: {
|
|
11915
|
+
type: "number",
|
|
11916
|
+
ops: ["connect"],
|
|
11917
|
+
description: "connect: Object.ConnectFlags, such as 1 for deferred."
|
|
11918
|
+
},
|
|
11919
|
+
nodePath: { type: "string", ops: ["list"], description: "list: only connections involving this node." }
|
|
11773
11920
|
},
|
|
11774
11921
|
requires: ["projectPath", "scenePath"],
|
|
11775
11922
|
operations: {
|
|
@@ -11894,18 +12041,28 @@ var init_tool_definitions = __esm(() => {
|
|
|
11894
12041
|
parameters: {
|
|
11895
12042
|
projectPath: PROJECT_PATH,
|
|
11896
12043
|
scriptPath: SCRIPT_PATH,
|
|
11897
|
-
className: {
|
|
11898
|
-
|
|
12044
|
+
className: {
|
|
12045
|
+
type: "string",
|
|
12046
|
+
ops: ["create"],
|
|
12047
|
+
description: "create: a class_name for the script. Not with content."
|
|
12048
|
+
},
|
|
12049
|
+
extends: {
|
|
12050
|
+
type: "string",
|
|
12051
|
+
ops: ["create"],
|
|
12052
|
+
description: "create: the base class. Default Node. Not with content."
|
|
12053
|
+
},
|
|
11899
12054
|
content: {
|
|
11900
12055
|
type: "string",
|
|
12056
|
+
ops: ["create"],
|
|
11901
12057
|
description: "create: the whole file, written as given, instead of a template, className and extends."
|
|
11902
12058
|
},
|
|
11903
12059
|
template: {
|
|
11904
12060
|
type: "string",
|
|
11905
12061
|
enum: ["singleton", "state_machine", "component", "resource"],
|
|
12062
|
+
ops: ["create"],
|
|
11906
12063
|
description: "create: a starting shape."
|
|
11907
12064
|
},
|
|
11908
|
-
modifications: SCRIPT_MODIFICATIONS,
|
|
12065
|
+
modifications: { ...SCRIPT_MODIFICATIONS, ops: ["modify"] },
|
|
11909
12066
|
symbol: {
|
|
11910
12067
|
type: "string",
|
|
11911
12068
|
ops: ["rename"],
|
|
@@ -11943,16 +12100,19 @@ var init_tool_definitions = __esm(() => {
|
|
|
11943
12100
|
scriptPath: SCRIPT_PATH,
|
|
11944
12101
|
includeInherited: {
|
|
11945
12102
|
type: "boolean",
|
|
12103
|
+
ops: ["structure"],
|
|
11946
12104
|
description: "structure: also list what the script inherits, walking extends through the project's other scripts. Each such member carries inherited_from, the file declaring it, and the answer gains inherits_from, the chain that was walked. A name the script overrides is listed at both its lines rather than once. A native base is not walked, since it declares nothing in a file: editor_classes info answers for those. Default false."
|
|
11947
12105
|
},
|
|
11948
12106
|
line: {
|
|
11949
12107
|
type: "integer",
|
|
11950
12108
|
minimum: 1,
|
|
12109
|
+
ops: ["completion", "hover"],
|
|
11951
12110
|
description: "completion, hover: the line, counted from 1 as the editor shows it."
|
|
11952
12111
|
},
|
|
11953
12112
|
character: {
|
|
11954
12113
|
type: "integer",
|
|
11955
12114
|
minimum: 1,
|
|
12115
|
+
ops: ["completion", "hover"],
|
|
11956
12116
|
description: "completion, hover: the column, counted from 1 as the editor shows it, with a tab counted as one. symbols gives each name at the line and column hover is asked at."
|
|
11957
12117
|
}
|
|
11958
12118
|
},
|
|
@@ -11960,7 +12120,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11960
12120
|
operations: {
|
|
11961
12121
|
structure: {
|
|
11962
12122
|
effect: "read",
|
|
11963
|
-
summary: "functions, variables, signals, class_name and extends, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class",
|
|
12123
|
+
summary: "functions, variables, signals, class_name and extends, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies",
|
|
11964
12124
|
requires: []
|
|
11965
12125
|
},
|
|
11966
12126
|
symbols: {
|
|
@@ -11995,7 +12155,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11995
12155
|
{
|
|
11996
12156
|
name: "resource_edit",
|
|
11997
12157
|
title: "Edit resources",
|
|
11998
|
-
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource
|
|
12158
|
+
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before the save, and a resource where that cannot be done is refused and the file left as it was.',
|
|
11999
12159
|
parameters: {
|
|
12000
12160
|
projectPath: PROJECT_PATH,
|
|
12001
12161
|
resourcePath: {
|
|
@@ -12005,7 +12165,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12005
12165
|
resourceType: {
|
|
12006
12166
|
type: "string",
|
|
12007
12167
|
ops: ["create"],
|
|
12008
|
-
description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D".'
|
|
12168
|
+
description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D", or a class_name a project script declares, which the resource is made as with that script attached.'
|
|
12009
12169
|
},
|
|
12010
12170
|
properties: { ...PROPERTIES, ops: ["create", "modify"] },
|
|
12011
12171
|
script: {
|
|
@@ -12199,6 +12359,17 @@ var init_tool_definitions = __esm(() => {
|
|
|
12199
12359
|
ops: ["profile"],
|
|
12200
12360
|
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed. A property's get or set block, which the profile names Class.@name_getter or Class.@name_setter, is asked about by that name or by the property's: the places are where the property is named, each saying instead of called whether it writes the property (access write), which runs the set block, or reads it (access read), which runs the get block, and the rows are both blocks'. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not places."
|
|
12201
12361
|
},
|
|
12362
|
+
mark: {
|
|
12363
|
+
type: "boolean",
|
|
12364
|
+
ops: ["profile"],
|
|
12365
|
+
description: `profile: answer as usual and set the run's mark at these totals, so a later profile with since: "mark" answers for one action, a press or a commit, apart from the boot and everything before it. The mark is kept beside the run's profile, so a server that replaces this one reads it too, and setting it again moves it. Given with since, the answer counts from the mark before it moves, which times one span after another. Default false.`
|
|
12366
|
+
},
|
|
12367
|
+
since: {
|
|
12368
|
+
type: "string",
|
|
12369
|
+
enum: ["mark"],
|
|
12370
|
+
ops: ["profile"],
|
|
12371
|
+
description: 'profile: "mark" answers only what was added after the mark that mark: true set in this run: per function the calls and self and total milliseconds since it, with the functions that added no call left out, frames and coveredMs since it, and callersOf read off the same difference. Refused when no mark was set in this run, or when the profile holds less than it did at the mark, which is a profiler started again since.'
|
|
12372
|
+
},
|
|
12202
12373
|
limit: {
|
|
12203
12374
|
type: "integer",
|
|
12204
12375
|
minimum: 1,
|
|
@@ -12250,7 +12421,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12250
12421
|
requires: []
|
|
12251
12422
|
}
|
|
12252
12423
|
},
|
|
12253
|
-
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
12424
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
|
|
12254
12425
|
defaultOperation: "start"
|
|
12255
12426
|
},
|
|
12256
12427
|
{
|
|
@@ -12346,9 +12517,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
12346
12517
|
description: "The engine's ClassDB: find classes, read one in full, or walk an inheritance tree.",
|
|
12347
12518
|
parameters: {
|
|
12348
12519
|
projectPath: PROJECT_PATH,
|
|
12349
|
-
filter: { type: "string", description: "query: a substring of the class name." },
|
|
12520
|
+
filter: { type: "string", ops: ["query"], description: "query: a substring of the class name." },
|
|
12350
12521
|
category: {
|
|
12351
12522
|
type: "string",
|
|
12523
|
+
ops: ["query"],
|
|
12352
12524
|
enum: [
|
|
12353
12525
|
"node",
|
|
12354
12526
|
"node2d",
|
|
@@ -12365,9 +12537,21 @@ var init_tool_definitions = __esm(() => {
|
|
|
12365
12537
|
],
|
|
12366
12538
|
description: "query: limit to one family."
|
|
12367
12539
|
},
|
|
12368
|
-
instantiableOnly: {
|
|
12369
|
-
|
|
12370
|
-
|
|
12540
|
+
instantiableOnly: {
|
|
12541
|
+
type: "boolean",
|
|
12542
|
+
ops: ["query"],
|
|
12543
|
+
description: "query: leave out abstract classes. Default false."
|
|
12544
|
+
},
|
|
12545
|
+
className: {
|
|
12546
|
+
type: "string",
|
|
12547
|
+
ops: ["info", "inheritance"],
|
|
12548
|
+
description: "info, inheritance: the class."
|
|
12549
|
+
},
|
|
12550
|
+
includeInherited: {
|
|
12551
|
+
type: "boolean",
|
|
12552
|
+
ops: ["info"],
|
|
12553
|
+
description: "info: include inherited members. Default false."
|
|
12554
|
+
},
|
|
12371
12555
|
member: {
|
|
12372
12556
|
type: "string",
|
|
12373
12557
|
ops: ["info"],
|
|
@@ -12521,7 +12705,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12521
12705
|
value: {
|
|
12522
12706
|
ops: ["set"],
|
|
12523
12707
|
blank: true,
|
|
12524
|
-
description: `set: the value, fitted to the property's type. A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
|
|
12708
|
+
description: `set: the value, fitted to the property's type. ${WHOLE_NUMBERS} A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent, a fraction into one holding an int included. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
|
|
12525
12709
|
},
|
|
12526
12710
|
method: {
|
|
12527
12711
|
type: "string",
|
|
@@ -12531,7 +12715,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12531
12715
|
args: {
|
|
12532
12716
|
type: "array",
|
|
12533
12717
|
ops: ["call"],
|
|
12534
|
-
description: `call: the arguments, fitted to the method's parameter types. A number a parameter would have to cut is refused rather than truncated: a fraction for an int, a number other than 0 or 1 for a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused, and so are too few or too many arguments. The engine names a parameter typed as a script class without class_name by the class the script extends, so an object of that class with another script passes that check and the engine refuses the call: the answer says the method did not run, with the engine's reason. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
|
|
12718
|
+
description: `call: the arguments, fitted to the method's parameter types. An RID is passed as the {"_type": "RID", "id": ...} a read answers it with, such as a viewport's for RenderingServer's render timing. ${WHOLE_NUMBERS} A number a parameter would have to cut is refused rather than truncated: a fraction for an int, a number other than 0 or 1 for a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused, and so are too few or too many arguments. The engine names a parameter typed as a script class without class_name by the class the script extends, so an object of that class with another script passes that check and the engine refuses the call: the answer says the method did not run, with the engine's reason. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
|
|
12535
12719
|
},
|
|
12536
12720
|
properties: {
|
|
12537
12721
|
type: ["boolean", "array"],
|
|
@@ -13035,8 +13219,11 @@ function headlessSection() {
|
|
|
13035
13219
|
"had none. The reading ops are the ones to gate on, and which is which is in each",
|
|
13036
13220
|
"op's own summary below rather than assumed from this list.",
|
|
13037
13221
|
"",
|
|
13038
|
-
"
|
|
13039
|
-
"
|
|
13222
|
+
"`scene_create`, `scene_node`, `scene_signal` and `resource_edit` go through the editor when one",
|
|
13223
|
+
"serves the project and through a headless engine when none does, so they need nothing open",
|
|
13224
|
+
"either; a gate can call them, and the reads among them are the ones to gate on. Everything else",
|
|
13225
|
+
"needs the editor (`editor_*`, `debug_*`, `scene_animation` but its reads) or a running game",
|
|
13226
|
+
"(`runtime_*`).",
|
|
13040
13227
|
"",
|
|
13041
13228
|
'One of these can be pointed the other way: `project_settings get` takes `from: "editor"` to ask',
|
|
13042
13229
|
"the open editor instead of the file, which is a different reading and costs no engine start. A",
|
|
@@ -32710,7 +32897,7 @@ var init_ajvProvider_CEoC__sr = __esm(() => {
|
|
|
32710
32897
|
});
|
|
32711
32898
|
|
|
32712
32899
|
// node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
|
|
32713
|
-
import
|
|
32900
|
+
import process10 from "node:process";
|
|
32714
32901
|
var init_shimsNode = __esm(() => {
|
|
32715
32902
|
init_ajvProvider_CEoC__sr();
|
|
32716
32903
|
});
|
|
@@ -34524,7 +34711,7 @@ var swallowsErrorsAfterClose, StdioServerTransport = class {
|
|
|
34524
34711
|
_readBuffer;
|
|
34525
34712
|
_started = false;
|
|
34526
34713
|
_closed = false;
|
|
34527
|
-
constructor(_stdin =
|
|
34714
|
+
constructor(_stdin = process10.stdin, _stdout = process10.stdout, options) {
|
|
34528
34715
|
this._stdin = _stdin;
|
|
34529
34716
|
this._stdout = _stdout;
|
|
34530
34717
|
this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
|
|
@@ -34713,10 +34900,10 @@ var init_stdio = __esm(() => {
|
|
|
34713
34900
|
});
|
|
34714
34901
|
|
|
34715
34902
|
// src/runtime-client.ts
|
|
34716
|
-
import { existsSync as
|
|
34903
|
+
import { existsSync as existsSync15, readdirSync as readdirSync10, readFileSync as readFileSync13, statSync as statSync8, unlinkSync as unlinkSync2 } from "node:fs";
|
|
34717
34904
|
import { createConnection } from "node:net";
|
|
34718
34905
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
34719
|
-
import { dirname as
|
|
34906
|
+
import { dirname as dirname10, join as join16, resolve as resolve4 } from "node:path";
|
|
34720
34907
|
import { setTimeout as delay } from "node:timers/promises";
|
|
34721
34908
|
function runtimeDirectory(variables = process.env) {
|
|
34722
34909
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -34724,20 +34911,20 @@ function runtimeDirectory(variables = process.env) {
|
|
|
34724
34911
|
return explicit;
|
|
34725
34912
|
}
|
|
34726
34913
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
34727
|
-
return
|
|
34914
|
+
return join16(perUser ?? tmpdir4(), "gdharness");
|
|
34728
34915
|
}
|
|
34729
34916
|
function runtimeDirectories(variables = process.env) {
|
|
34730
34917
|
const candidates = [runtimeDirectory(variables)];
|
|
34731
34918
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
34732
34919
|
for (const base of fallbacks) {
|
|
34733
|
-
candidates.push(
|
|
34920
|
+
candidates.push(join16(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
34734
34921
|
}
|
|
34735
34922
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
34736
34923
|
}
|
|
34737
34924
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
34738
34925
|
for (const directory of directories) {
|
|
34739
|
-
const path =
|
|
34740
|
-
if (
|
|
34926
|
+
const path = join16(directory, `runtime-${pid}.log`);
|
|
34927
|
+
if (existsSync15(path)) {
|
|
34741
34928
|
return path;
|
|
34742
34929
|
}
|
|
34743
34930
|
}
|
|
@@ -34785,7 +34972,7 @@ function parseAnnouncement(file, pid) {
|
|
|
34785
34972
|
};
|
|
34786
34973
|
let announcedAt;
|
|
34787
34974
|
try {
|
|
34788
|
-
announcedAt =
|
|
34975
|
+
announcedAt = statSync8(file).mtimeMs;
|
|
34789
34976
|
} catch {}
|
|
34790
34977
|
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
34791
34978
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
@@ -34877,29 +35064,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
34877
35064
|
}).map((one) => one.file));
|
|
34878
35065
|
}
|
|
34879
35066
|
function announcedIn(directory) {
|
|
34880
|
-
if (!
|
|
35067
|
+
if (!existsSync15(directory)) {
|
|
34881
35068
|
return [];
|
|
34882
35069
|
}
|
|
34883
35070
|
const found = [];
|
|
34884
35071
|
const candidates = [];
|
|
34885
35072
|
const entries = [];
|
|
34886
|
-
for (const entry of
|
|
35073
|
+
for (const entry of readdirSync10(directory)) {
|
|
34887
35074
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
34888
35075
|
if (report) {
|
|
34889
|
-
sweepErrorReport(
|
|
35076
|
+
sweepErrorReport(join16(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
34890
35077
|
continue;
|
|
34891
35078
|
}
|
|
34892
35079
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
34893
35080
|
if (!match) {
|
|
34894
35081
|
continue;
|
|
34895
35082
|
}
|
|
34896
|
-
const file =
|
|
35083
|
+
const file = join16(directory, entry);
|
|
34897
35084
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
34898
35085
|
const running = alive(pid);
|
|
34899
35086
|
entries.push({ file, pid, running });
|
|
34900
35087
|
if (running) {
|
|
34901
35088
|
try {
|
|
34902
|
-
candidates.push({ file, pid, writtenAt:
|
|
35089
|
+
candidates.push({ file, pid, writtenAt: statSync8(file).mtimeMs });
|
|
34903
35090
|
} catch {}
|
|
34904
35091
|
}
|
|
34905
35092
|
}
|
|
@@ -34921,16 +35108,16 @@ function announcedIn(directory) {
|
|
|
34921
35108
|
found.push(announced);
|
|
34922
35109
|
}
|
|
34923
35110
|
const held = new Set(entries.map((one) => one.file));
|
|
34924
|
-
const here =
|
|
35111
|
+
const here = join16(directory, ".");
|
|
34925
35112
|
for (const file of verdicts.keys()) {
|
|
34926
|
-
if (
|
|
35113
|
+
if (dirname10(file) === here && !held.has(file)) {
|
|
34927
35114
|
verdicts.delete(file);
|
|
34928
35115
|
}
|
|
34929
35116
|
}
|
|
34930
35117
|
return found;
|
|
34931
35118
|
}
|
|
34932
35119
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
34933
|
-
const files = directories.map((directory) =>
|
|
35120
|
+
const files = directories.map((directory) => join16(directory, `runtime-${pid}.json`)).filter((file) => existsSync15(file));
|
|
34934
35121
|
const first = files[0];
|
|
34935
35122
|
if (first === undefined) {
|
|
34936
35123
|
return;
|
|
@@ -34956,7 +35143,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
34956
35143
|
return;
|
|
34957
35144
|
}
|
|
34958
35145
|
try {
|
|
34959
|
-
if (now -
|
|
35146
|
+
if (now - statSync8(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
34960
35147
|
unlinkSync2(path);
|
|
34961
35148
|
}
|
|
34962
35149
|
} catch {}
|
|
@@ -35232,9 +35419,9 @@ var init_runtime_client = __esm(() => {
|
|
|
35232
35419
|
|
|
35233
35420
|
// src/boot-note.ts
|
|
35234
35421
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
|
|
35235
|
-
import { dirname as
|
|
35422
|
+
import { dirname as dirname11, join as join17 } from "node:path";
|
|
35236
35423
|
function bootNotePath(projectPath) {
|
|
35237
|
-
return
|
|
35424
|
+
return join17(projectPath, ".godot", "gdharness-boot.json");
|
|
35238
35425
|
}
|
|
35239
35426
|
function readScenes(projectPath) {
|
|
35240
35427
|
try {
|
|
@@ -35254,7 +35441,7 @@ function readBootNote(projectPath, scene) {
|
|
|
35254
35441
|
function writeBootNote(projectPath, scene, announcedAfterMs) {
|
|
35255
35442
|
try {
|
|
35256
35443
|
const path = bootNotePath(projectPath);
|
|
35257
|
-
mkdirSync6(
|
|
35444
|
+
mkdirSync6(dirname11(path), { recursive: true });
|
|
35258
35445
|
const scenes = {
|
|
35259
35446
|
...readScenes(projectPath),
|
|
35260
35447
|
[scene]: {
|
|
@@ -35281,9 +35468,9 @@ var init_boot_note = __esm(() => {
|
|
|
35281
35468
|
|
|
35282
35469
|
// src/breakpoint-note.ts
|
|
35283
35470
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
35284
|
-
import { dirname as
|
|
35471
|
+
import { dirname as dirname12, isAbsolute as isAbsolute4, join as join18, relative as relative3 } from "node:path";
|
|
35285
35472
|
function breakpointNotePath(projectPath) {
|
|
35286
|
-
return
|
|
35473
|
+
return join18(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
35287
35474
|
}
|
|
35288
35475
|
function writeBreakpointNote(projectPath, held) {
|
|
35289
35476
|
const path = breakpointNotePath(projectPath);
|
|
@@ -35300,7 +35487,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
35300
35487
|
lines: [...one.lines]
|
|
35301
35488
|
}))
|
|
35302
35489
|
};
|
|
35303
|
-
mkdirSync7(
|
|
35490
|
+
mkdirSync7(dirname12(path), { recursive: true });
|
|
35304
35491
|
writeFileSync9(path, `${JSON.stringify(note, null, 2)}
|
|
35305
35492
|
`, "utf8");
|
|
35306
35493
|
} catch (error) {
|
|
@@ -35319,12 +35506,12 @@ function readBreakpointNote(projectPath) {
|
|
|
35319
35506
|
continue;
|
|
35320
35507
|
}
|
|
35321
35508
|
const { script, lines: listed } = entry;
|
|
35322
|
-
if (typeof script !== "string" ||
|
|
35509
|
+
if (typeof script !== "string" || isAbsolute4(script) || script.split("/").includes("..")) {
|
|
35323
35510
|
continue;
|
|
35324
35511
|
}
|
|
35325
35512
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
35326
35513
|
if (lines.length > 0) {
|
|
35327
|
-
held.push({ scriptPath: realPathOr(
|
|
35514
|
+
held.push({ scriptPath: realPathOr(join18(projectPath, script)), lines });
|
|
35328
35515
|
}
|
|
35329
35516
|
}
|
|
35330
35517
|
return held;
|
|
@@ -35334,7 +35521,7 @@ function readBreakpointNote(projectPath) {
|
|
|
35334
35521
|
}
|
|
35335
35522
|
function withinProject(projectPath, scriptPath) {
|
|
35336
35523
|
const rel = relative3(projectPath, scriptPath);
|
|
35337
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
35524
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute4(rel);
|
|
35338
35525
|
}
|
|
35339
35526
|
var init_breakpoint_note = __esm(() => {
|
|
35340
35527
|
init_errors();
|
|
@@ -35374,9 +35561,9 @@ var init_whole_file = __esm(() => {
|
|
|
35374
35561
|
|
|
35375
35562
|
// src/class-note.ts
|
|
35376
35563
|
import { mkdirSync as mkdirSync8, readFileSync as readFileSync16 } from "node:fs";
|
|
35377
|
-
import { dirname as
|
|
35564
|
+
import { dirname as dirname13, join as join19 } from "node:path";
|
|
35378
35565
|
function classNotePath(projectPath) {
|
|
35379
|
-
return
|
|
35566
|
+
return join19(projectPath, ".godot", "gdharness-classes.json");
|
|
35380
35567
|
}
|
|
35381
35568
|
function readClassNote(projectPath) {
|
|
35382
35569
|
try {
|
|
@@ -35410,7 +35597,7 @@ function unfinishedScanIn(value) {
|
|
|
35410
35597
|
function writeClassNote(projectPath, note) {
|
|
35411
35598
|
const path = classNotePath(projectPath);
|
|
35412
35599
|
try {
|
|
35413
|
-
mkdirSync8(
|
|
35600
|
+
mkdirSync8(dirname13(path), { recursive: true });
|
|
35414
35601
|
} catch {
|
|
35415
35602
|
return;
|
|
35416
35603
|
}
|
|
@@ -35422,11 +35609,11 @@ var init_class_note = __esm(() => {
|
|
|
35422
35609
|
});
|
|
35423
35610
|
|
|
35424
35611
|
// src/config-pin.ts
|
|
35425
|
-
import { existsSync as
|
|
35426
|
-
import { join as
|
|
35612
|
+
import { existsSync as existsSync16, readFileSync as readFileSync17 } from "node:fs";
|
|
35613
|
+
import { join as join20 } from "node:path";
|
|
35427
35614
|
function versionsNamedInConfig(projectPath) {
|
|
35428
|
-
const config =
|
|
35429
|
-
if (!
|
|
35615
|
+
const config = join20(projectPath, ".mcp.json");
|
|
35616
|
+
if (!existsSync16(config)) {
|
|
35430
35617
|
return [];
|
|
35431
35618
|
}
|
|
35432
35619
|
let text;
|
|
@@ -35452,22 +35639,22 @@ var init_config_pin = __esm(() => {
|
|
|
35452
35639
|
});
|
|
35453
35640
|
|
|
35454
35641
|
// src/cut-test-run.ts
|
|
35455
|
-
import { existsSync as
|
|
35456
|
-
import { dirname as
|
|
35642
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
|
|
35643
|
+
import { dirname as dirname14, join as join21 } from "node:path";
|
|
35457
35644
|
function keptAt(projectPath) {
|
|
35458
|
-
return
|
|
35645
|
+
return join21(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
35459
35646
|
}
|
|
35460
35647
|
function keepCutTestRun(projectPath, run) {
|
|
35461
35648
|
const path = keptAt(projectPath);
|
|
35462
35649
|
try {
|
|
35463
|
-
mkdirSync9(
|
|
35650
|
+
mkdirSync9(dirname14(path), { recursive: true });
|
|
35464
35651
|
writeFileSync11(path, `${JSON.stringify(run, null, 2)}
|
|
35465
35652
|
`, "utf8");
|
|
35466
35653
|
} catch {}
|
|
35467
35654
|
}
|
|
35468
35655
|
function takeCutTestRun(projectPath) {
|
|
35469
35656
|
const path = keptAt(projectPath);
|
|
35470
|
-
if (!
|
|
35657
|
+
if (!existsSync17(path)) {
|
|
35471
35658
|
return null;
|
|
35472
35659
|
}
|
|
35473
35660
|
try {
|
|
@@ -36284,27 +36471,27 @@ var init_dap_client = __esm(() => {
|
|
|
36284
36471
|
import { createHash as createHash4 } from "node:crypto";
|
|
36285
36472
|
import {
|
|
36286
36473
|
closeSync,
|
|
36287
|
-
existsSync as
|
|
36474
|
+
existsSync as existsSync18,
|
|
36288
36475
|
mkdtempSync as mkdtempSync2,
|
|
36289
36476
|
openSync,
|
|
36290
36477
|
readFileSync as readFileSync19,
|
|
36291
36478
|
rmSync as rmSync10,
|
|
36292
|
-
statSync as
|
|
36479
|
+
statSync as statSync9,
|
|
36293
36480
|
writeFileSync as writeFileSync12,
|
|
36294
36481
|
writeSync
|
|
36295
36482
|
} from "node:fs";
|
|
36296
36483
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
36297
|
-
import { join as
|
|
36484
|
+
import { join as join22 } from "node:path";
|
|
36298
36485
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
36299
36486
|
function helperAssembly(directory = tmpdir5()) {
|
|
36300
|
-
return
|
|
36487
|
+
return join22(directory, `gdharness-desktop-${DIGEST}.dll`);
|
|
36301
36488
|
}
|
|
36302
36489
|
async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COMPILE_MS, wait = true) {
|
|
36303
36490
|
const assembly = helperAssembly(directory);
|
|
36304
36491
|
const lock = `${assembly}.compiling`;
|
|
36305
36492
|
const until = Date.now() + budgetMs;
|
|
36306
36493
|
while (Date.now() < until) {
|
|
36307
|
-
if (
|
|
36494
|
+
if (existsSync18(assembly)) {
|
|
36308
36495
|
return null;
|
|
36309
36496
|
}
|
|
36310
36497
|
let mine = false;
|
|
@@ -36323,7 +36510,7 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
|
|
|
36323
36510
|
}
|
|
36324
36511
|
try {
|
|
36325
36512
|
const holder = Number(readFileSync19(lock, "utf8").trim());
|
|
36326
|
-
const old = Date.now() -
|
|
36513
|
+
const old = Date.now() - statSync9(lock).mtimeMs > budgetMs;
|
|
36327
36514
|
if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
|
|
36328
36515
|
rmSync10(lock, { force: true });
|
|
36329
36516
|
continue;
|
|
@@ -36337,8 +36524,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
|
|
|
36337
36524
|
return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
|
|
36338
36525
|
}
|
|
36339
36526
|
async function compileHelper(directory, assembly, budgetMs) {
|
|
36340
|
-
const scratch = mkdtempSync2(
|
|
36341
|
-
const source =
|
|
36527
|
+
const scratch = mkdtempSync2(join22(directory, "gdharness-desktop-source-"));
|
|
36528
|
+
const source = join22(scratch, "helper.cs");
|
|
36342
36529
|
writeFileSync12(source, SOURCE, "utf8");
|
|
36343
36530
|
const literal = (path) => `'${path.replaceAll("'", "''")}'`;
|
|
36344
36531
|
const script = [
|
|
@@ -36376,7 +36563,7 @@ async function compileHelper(directory, assembly, budgetMs) {
|
|
|
36376
36563
|
});
|
|
36377
36564
|
compiler.once("close", (code) => {
|
|
36378
36565
|
clearTimeout(timer);
|
|
36379
|
-
resolve(
|
|
36566
|
+
resolve(existsSync18(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
|
|
36380
36567
|
});
|
|
36381
36568
|
});
|
|
36382
36569
|
} finally {
|
|
@@ -36543,21 +36730,21 @@ var init_desktop = __esm(() => {
|
|
|
36543
36730
|
// src/editor-log.ts
|
|
36544
36731
|
import {
|
|
36545
36732
|
closeSync as closeSync2,
|
|
36546
|
-
existsSync as
|
|
36733
|
+
existsSync as existsSync19,
|
|
36547
36734
|
mkdirSync as mkdirSync10,
|
|
36548
36735
|
openSync as openSync2,
|
|
36549
36736
|
readFileSync as readFileSync20,
|
|
36550
36737
|
readSync,
|
|
36551
36738
|
rmSync as rmSync11,
|
|
36552
|
-
statSync as
|
|
36739
|
+
statSync as statSync10,
|
|
36553
36740
|
writeFileSync as writeFileSync13
|
|
36554
36741
|
} from "node:fs";
|
|
36555
|
-
import { dirname as
|
|
36742
|
+
import { dirname as dirname15, join as join23 } from "node:path";
|
|
36556
36743
|
function editorLogPath(projectPath) {
|
|
36557
|
-
return
|
|
36744
|
+
return join23(projectPath, ".godot", "gdharness-editor.log");
|
|
36558
36745
|
}
|
|
36559
36746
|
function editorLogNotePath(projectPath) {
|
|
36560
|
-
return
|
|
36747
|
+
return join23(projectPath, ".godot", "gdharness-editor.json");
|
|
36561
36748
|
}
|
|
36562
36749
|
function readEditorLogNote(projectPath) {
|
|
36563
36750
|
try {
|
|
@@ -36572,7 +36759,7 @@ function readEditorLogNote(projectPath) {
|
|
|
36572
36759
|
function clearEditorLog(projectPath) {
|
|
36573
36760
|
try {
|
|
36574
36761
|
const path = editorLogPath(projectPath);
|
|
36575
|
-
mkdirSync10(
|
|
36762
|
+
mkdirSync10(dirname15(path), { recursive: true });
|
|
36576
36763
|
rmSync11(path, { force: true });
|
|
36577
36764
|
rmSync11(editorLogNotePath(projectPath), { force: true });
|
|
36578
36765
|
} catch {}
|
|
@@ -36580,7 +36767,7 @@ function clearEditorLog(projectPath) {
|
|
|
36580
36767
|
function writeEditorLogNote(projectPath, pid) {
|
|
36581
36768
|
try {
|
|
36582
36769
|
const path = editorLogNotePath(projectPath);
|
|
36583
|
-
mkdirSync10(
|
|
36770
|
+
mkdirSync10(dirname15(path), { recursive: true });
|
|
36584
36771
|
writeFileSync13(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
36585
36772
|
} catch {}
|
|
36586
36773
|
}
|
|
@@ -36602,7 +36789,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
36602
36789
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
36603
36790
|
}
|
|
36604
36791
|
const path = editorLogPath(projectPath);
|
|
36605
|
-
if (!
|
|
36792
|
+
if (!existsSync19(path)) {
|
|
36606
36793
|
return { kind: "not written" };
|
|
36607
36794
|
}
|
|
36608
36795
|
const log = new GameLog;
|
|
@@ -36658,7 +36845,7 @@ function foldProgress(text) {
|
|
|
36658
36845
|
`);
|
|
36659
36846
|
}
|
|
36660
36847
|
function theFirstOf(path) {
|
|
36661
|
-
const whole =
|
|
36848
|
+
const whole = statSync10(path).size;
|
|
36662
36849
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
36663
36850
|
return { text: readFileSync20(path, "utf8") };
|
|
36664
36851
|
}
|
|
@@ -36821,8 +37008,8 @@ var init_editor_log = __esm(() => {
|
|
|
36821
37008
|
});
|
|
36822
37009
|
|
|
36823
37010
|
// src/rename.ts
|
|
36824
|
-
import { existsSync as
|
|
36825
|
-
import { dirname as
|
|
37011
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync21, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
|
|
37012
|
+
import { dirname as dirname16, posix as posix2 } from "node:path";
|
|
36826
37013
|
|
|
36827
37014
|
class Script {
|
|
36828
37015
|
path;
|
|
@@ -37530,21 +37717,21 @@ function applyRewrites(rewrites) {
|
|
|
37530
37717
|
if (now !== rewrite.before) {
|
|
37531
37718
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
37532
37719
|
}
|
|
37533
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
37720
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync20(rewrite.writeTo)) {
|
|
37534
37721
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
37535
37722
|
}
|
|
37536
37723
|
}
|
|
37537
37724
|
const done = [];
|
|
37538
37725
|
try {
|
|
37539
37726
|
for (const rewrite of rewrites) {
|
|
37540
|
-
mkdirSync11(
|
|
37727
|
+
mkdirSync11(dirname16(rewrite.writeTo), { recursive: true });
|
|
37541
37728
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
37542
37729
|
writeFileSync14(staging, rewrite.after, "utf8");
|
|
37543
37730
|
renameSync3(staging, rewrite.writeTo);
|
|
37544
37731
|
let movedUid = false;
|
|
37545
37732
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
37546
37733
|
unlinkSync3(rewrite.absolute);
|
|
37547
|
-
if (
|
|
37734
|
+
if (existsSync20(`${rewrite.absolute}.uid`)) {
|
|
37548
37735
|
renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
37549
37736
|
movedUid = true;
|
|
37550
37737
|
}
|
|
@@ -37590,10 +37777,10 @@ var init_rename = __esm(() => {
|
|
|
37590
37777
|
});
|
|
37591
37778
|
|
|
37592
37779
|
// src/lsp_client.ts
|
|
37593
|
-
import { existsSync as
|
|
37780
|
+
import { existsSync as existsSync21, realpathSync as realpathSync4 } from "node:fs";
|
|
37594
37781
|
import { readFile, realpath } from "node:fs/promises";
|
|
37595
37782
|
import { createConnection as createConnection3 } from "node:net";
|
|
37596
|
-
import { dirname as
|
|
37783
|
+
import { dirname as dirname17, join as join24, resolve as resolve5 } from "node:path";
|
|
37597
37784
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
37598
37785
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
37599
37786
|
function initializeTimeoutMs() {
|
|
@@ -37755,7 +37942,7 @@ class GodotLSPClient {
|
|
|
37755
37942
|
if (this.initialized) {
|
|
37756
37943
|
return;
|
|
37757
37944
|
}
|
|
37758
|
-
const rootPath = this.rootPath ??
|
|
37945
|
+
const rootPath = this.rootPath ?? dirname17(resolve5(filePath));
|
|
37759
37946
|
await this.initialize(rootPath);
|
|
37760
37947
|
}
|
|
37761
37948
|
async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
|
|
@@ -38271,7 +38458,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
38271
38458
|
} catch {
|
|
38272
38459
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
38273
38460
|
}
|
|
38274
|
-
if (!
|
|
38461
|
+
if (!existsSync21(join24(projectPath, "project.godot"))) {
|
|
38275
38462
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
38276
38463
|
}
|
|
38277
38464
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -38431,8 +38618,8 @@ var init_lsp_client = __esm(() => {
|
|
|
38431
38618
|
});
|
|
38432
38619
|
|
|
38433
38620
|
// src/rename-symbol.ts
|
|
38434
|
-
import { existsSync as
|
|
38435
|
-
import { basename as
|
|
38621
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readdirSync as readdirSync11, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
|
|
38622
|
+
import { basename as basename7, dirname as dirname18, join as join25, relative as relative4, resolve as resolve6 } from "node:path";
|
|
38436
38623
|
function resourcePathOf(projectPath, absolute) {
|
|
38437
38624
|
const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
38438
38625
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
@@ -38445,13 +38632,13 @@ function onDiskSpelling(path) {
|
|
|
38445
38632
|
let at = resolve6(path);
|
|
38446
38633
|
for (;; ) {
|
|
38447
38634
|
try {
|
|
38448
|
-
return
|
|
38635
|
+
return join25(realpathSync5.native(at), ...missing.reverse());
|
|
38449
38636
|
} catch {
|
|
38450
|
-
const parent =
|
|
38637
|
+
const parent = dirname18(at);
|
|
38451
38638
|
if (parent === at) {
|
|
38452
38639
|
return resolve6(path);
|
|
38453
38640
|
}
|
|
38454
|
-
missing.push(
|
|
38641
|
+
missing.push(basename7(at));
|
|
38455
38642
|
at = parent;
|
|
38456
38643
|
}
|
|
38457
38644
|
}
|
|
@@ -38498,7 +38685,7 @@ async function renameSymbol(request, services) {
|
|
|
38498
38685
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
38499
38686
|
};
|
|
38500
38687
|
}
|
|
38501
|
-
if (
|
|
38688
|
+
if (existsSync22(request.newScriptPath)) {
|
|
38502
38689
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
38503
38690
|
}
|
|
38504
38691
|
}
|
|
@@ -38674,7 +38861,7 @@ async function renameSymbol(request, services) {
|
|
|
38674
38861
|
scripts,
|
|
38675
38862
|
moves: new Map([[declaringPath, moveTo]]),
|
|
38676
38863
|
named: [declaringPath],
|
|
38677
|
-
exists: (path) =>
|
|
38864
|
+
exists: (path) => existsSync22(`${projectPath}/${path.slice("res://".length)}`)
|
|
38678
38865
|
}, plan);
|
|
38679
38866
|
}
|
|
38680
38867
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
|
|
@@ -38873,15 +39060,15 @@ ${mention.kind}`;
|
|
|
38873
39060
|
};
|
|
38874
39061
|
}
|
|
38875
39062
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
38876
|
-
const directory =
|
|
39063
|
+
const directory = join25(projectPath, ".godot", "gdharness-renames");
|
|
38877
39064
|
mkdirSync12(directory, { recursive: true });
|
|
38878
39065
|
detailWrites += 1;
|
|
38879
|
-
const path =
|
|
39066
|
+
const path = join25(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
38880
39067
|
writeFileSync15(path, `${JSON.stringify(detail, null, 2)}
|
|
38881
39068
|
`, "utf8");
|
|
38882
|
-
const kept =
|
|
39069
|
+
const kept = readdirSync11(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
38883
39070
|
for (const old of kept.slice(10)) {
|
|
38884
|
-
rmSync12(
|
|
39071
|
+
rmSync12(join25(directory, old), { force: true });
|
|
38885
39072
|
}
|
|
38886
39073
|
return path;
|
|
38887
39074
|
}
|
|
@@ -38922,25 +39109,25 @@ var init_rename_symbol = __esm(() => {
|
|
|
38922
39109
|
|
|
38923
39110
|
// src/file-move.ts
|
|
38924
39111
|
import {
|
|
38925
|
-
existsSync as
|
|
39112
|
+
existsSync as existsSync23,
|
|
38926
39113
|
mkdirSync as mkdirSync13,
|
|
38927
|
-
readdirSync as
|
|
39114
|
+
readdirSync as readdirSync12,
|
|
38928
39115
|
readFileSync as readFileSync22,
|
|
38929
39116
|
renameSync as renameSync4,
|
|
38930
|
-
statSync as
|
|
39117
|
+
statSync as statSync11,
|
|
38931
39118
|
writeFileSync as writeFileSync16
|
|
38932
39119
|
} from "node:fs";
|
|
38933
|
-
import { basename as
|
|
39120
|
+
import { basename as basename8, dirname as dirname19, extname as extname3, join as join26, relative as relative5 } from "node:path";
|
|
38934
39121
|
function filesUnder(directory) {
|
|
38935
|
-
return
|
|
38936
|
-
const path =
|
|
39122
|
+
return readdirSync12(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
39123
|
+
const path = join26(directory, entry.name);
|
|
38937
39124
|
return entry.isDirectory() ? filesUnder(path) : [path];
|
|
38938
39125
|
});
|
|
38939
39126
|
}
|
|
38940
39127
|
function sameFile(one, other) {
|
|
38941
39128
|
try {
|
|
38942
|
-
const a =
|
|
38943
|
-
const b =
|
|
39129
|
+
const a = statSync11(one);
|
|
39130
|
+
const b = statSync11(other);
|
|
38944
39131
|
return a.ino === b.ino && a.dev === b.dev;
|
|
38945
39132
|
} catch {
|
|
38946
39133
|
return false;
|
|
@@ -38954,8 +39141,8 @@ function unseenByTheEngine(projectPath, path) {
|
|
|
38954
39141
|
return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
|
|
38955
39142
|
}
|
|
38956
39143
|
if (at < steps.length - 1) {
|
|
38957
|
-
directory =
|
|
38958
|
-
if (
|
|
39144
|
+
directory = join26(directory, step);
|
|
39145
|
+
if (existsSync23(join26(directory, ".gdignore"))) {
|
|
38959
39146
|
return `${path} is under a directory holding a .gdignore, which the engine does not load`;
|
|
38960
39147
|
}
|
|
38961
39148
|
}
|
|
@@ -38967,21 +39154,21 @@ function installedAddonOf(projectPath, path) {
|
|
|
38967
39154
|
if (steps[0] !== "addons" || steps[1] === undefined) {
|
|
38968
39155
|
return null;
|
|
38969
39156
|
}
|
|
38970
|
-
return
|
|
39157
|
+
return existsSync23(join26(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
|
|
38971
39158
|
}
|
|
38972
39159
|
function binaryResources(projectPath) {
|
|
38973
39160
|
const found = [];
|
|
38974
39161
|
const visit = (directory, prefix) => {
|
|
38975
|
-
if (
|
|
39162
|
+
if (existsSync23(join26(directory, ".gdignore"))) {
|
|
38976
39163
|
return;
|
|
38977
39164
|
}
|
|
38978
|
-
for (const entry of
|
|
39165
|
+
for (const entry of readdirSync12(directory, { withFileTypes: true })) {
|
|
38979
39166
|
if (entry.name.startsWith(".")) {
|
|
38980
39167
|
continue;
|
|
38981
39168
|
}
|
|
38982
39169
|
const path = `${prefix}${entry.name}`;
|
|
38983
39170
|
if (entry.isDirectory()) {
|
|
38984
|
-
visit(
|
|
39171
|
+
visit(join26(directory, entry.name), `${path}/`);
|
|
38985
39172
|
} else if (/\.(scn|res)$/i.test(entry.name)) {
|
|
38986
39173
|
found.push(path);
|
|
38987
39174
|
}
|
|
@@ -38991,7 +39178,7 @@ function binaryResources(projectPath) {
|
|
|
38991
39178
|
return found;
|
|
38992
39179
|
}
|
|
38993
39180
|
function targetPathOf(projectPath, absolute) {
|
|
38994
|
-
const inside = relative5(onDiskSpelling(projectPath),
|
|
39181
|
+
const inside = relative5(onDiskSpelling(projectPath), join26(onDiskSpelling(dirname19(absolute)), basename8(absolute)));
|
|
38995
39182
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
38996
39183
|
return null;
|
|
38997
39184
|
}
|
|
@@ -39009,13 +39196,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39009
39196
|
if (to === null) {
|
|
39010
39197
|
return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
|
|
39011
39198
|
}
|
|
39012
|
-
if (!
|
|
39199
|
+
if (!existsSync23(absoluteFrom)) {
|
|
39013
39200
|
return { ok: false, reason: `There is nothing at ${from} to move.` };
|
|
39014
39201
|
}
|
|
39015
39202
|
if (from === to) {
|
|
39016
39203
|
return { ok: false, reason: `${from} is already there; nothing to move.` };
|
|
39017
39204
|
}
|
|
39018
|
-
const isDirectory =
|
|
39205
|
+
const isDirectory = statSync11(absoluteFrom).isDirectory();
|
|
39019
39206
|
if (from === "res://project.godot") {
|
|
39020
39207
|
return {
|
|
39021
39208
|
ok: false,
|
|
@@ -39038,7 +39225,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39038
39225
|
if (isDirectory && to.startsWith(`${from}/`)) {
|
|
39039
39226
|
return { ok: false, reason: `${from} cannot be moved into itself.` };
|
|
39040
39227
|
}
|
|
39041
|
-
if (
|
|
39228
|
+
if (existsSync23(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
|
|
39042
39229
|
return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
|
|
39043
39230
|
}
|
|
39044
39231
|
const unseen = unseenByTheEngine(projectPath, to);
|
|
@@ -39046,7 +39233,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39046
39233
|
return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
|
|
39047
39234
|
}
|
|
39048
39235
|
if (!isDirectory) {
|
|
39049
|
-
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null &&
|
|
39236
|
+
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync23(join26(projectPath, path.slice("res://".length))));
|
|
39050
39237
|
if (owner !== undefined && owner !== null) {
|
|
39051
39238
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
39052
39239
|
}
|
|
@@ -39068,7 +39255,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39068
39255
|
} else {
|
|
39069
39256
|
moves.set(from, to);
|
|
39070
39257
|
for (const sidecar of SIDECARS) {
|
|
39071
|
-
if (
|
|
39258
|
+
if (existsSync23(`${absoluteFrom}${sidecar}`)) {
|
|
39072
39259
|
moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
|
|
39073
39260
|
}
|
|
39074
39261
|
}
|
|
@@ -39080,12 +39267,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39080
39267
|
scripts,
|
|
39081
39268
|
moves,
|
|
39082
39269
|
named: [from],
|
|
39083
|
-
exists: (path) =>
|
|
39270
|
+
exists: (path) => existsSync23(join26(projectPath, path.slice("res://".length)))
|
|
39084
39271
|
}, { edits: [], mentions: [] });
|
|
39085
39272
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
|
|
39086
39273
|
const binaries = [];
|
|
39087
39274
|
for (const path of binaryResources(projectPath)) {
|
|
39088
|
-
const bytes = readFileSync22(
|
|
39275
|
+
const bytes = readFileSync22(join26(projectPath, path.slice("res://".length)));
|
|
39089
39276
|
const names = [...moves.keys()].find((moving) => bytes.includes(moving));
|
|
39090
39277
|
if (names !== undefined) {
|
|
39091
39278
|
binaries.push({
|
|
@@ -39104,7 +39291,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39104
39291
|
}));
|
|
39105
39292
|
const left = leftAloneReport(mentions);
|
|
39106
39293
|
const rewrites = rewritesOf(plan, texts, null);
|
|
39107
|
-
const absoluteOf = (path) =>
|
|
39294
|
+
const absoluteOf = (path) => join26(projectPath, path.slice("res://".length));
|
|
39108
39295
|
const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
|
|
39109
39296
|
const moved = [...moves].map(([source, target]) => {
|
|
39110
39297
|
const rewrite = rewritten.get(source);
|
|
@@ -39151,19 +39338,19 @@ function applyFileMove(outcome) {
|
|
|
39151
39338
|
}
|
|
39152
39339
|
}
|
|
39153
39340
|
for (const file of outcome.moved) {
|
|
39154
|
-
if (!
|
|
39341
|
+
if (!existsSync23(file.absoluteFrom)) {
|
|
39155
39342
|
throw new Error(`${file.from} is gone from disk; nothing was written`);
|
|
39156
39343
|
}
|
|
39157
39344
|
if (file.text !== null && readFileSync22(file.absoluteFrom, "utf8") !== file.text.before) {
|
|
39158
39345
|
throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
|
|
39159
39346
|
}
|
|
39160
|
-
if (
|
|
39347
|
+
if (existsSync23(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
|
|
39161
39348
|
throw new Error(`${file.to} already exists; nothing was written`);
|
|
39162
39349
|
}
|
|
39163
39350
|
}
|
|
39164
39351
|
const undo = [];
|
|
39165
39352
|
const rename = (source, target) => {
|
|
39166
|
-
mkdirSync13(
|
|
39353
|
+
mkdirSync13(dirname19(target), { recursive: true });
|
|
39167
39354
|
renameSync4(source, target);
|
|
39168
39355
|
undo.push(() => {
|
|
39169
39356
|
renameSync4(target, source);
|
|
@@ -43102,6 +43289,29 @@ ${note}`;
|
|
|
43102
43289
|
return detail === entry.detail ? entry : { ...entry, detail };
|
|
43103
43290
|
});
|
|
43104
43291
|
}
|
|
43292
|
+
function shortened(detail) {
|
|
43293
|
+
if (detail === null || detail.length <= LONGEST_DETAIL) {
|
|
43294
|
+
return detail;
|
|
43295
|
+
}
|
|
43296
|
+
return `${detail.slice(0, LONGEST_DETAIL)}
|
|
43297
|
+
[${detail.length - LONGEST_DETAIL} more characters left out]`;
|
|
43298
|
+
}
|
|
43299
|
+
function foldedReports(reports) {
|
|
43300
|
+
const first = new Map;
|
|
43301
|
+
for (const report of reports) {
|
|
43302
|
+
const key = JSON.stringify([report.kind, report.message, report.detail]);
|
|
43303
|
+
const seen = first.get(key);
|
|
43304
|
+
if (seen === undefined) {
|
|
43305
|
+
first.set(key, { report, times: 1 });
|
|
43306
|
+
} else {
|
|
43307
|
+
seen.times += 1;
|
|
43308
|
+
}
|
|
43309
|
+
}
|
|
43310
|
+
return [...first.values()].map(({ report, times }) => {
|
|
43311
|
+
const kept = { ...report, detail: shortened(report.detail) };
|
|
43312
|
+
return times === 1 ? kept : { ...kept, times };
|
|
43313
|
+
});
|
|
43314
|
+
}
|
|
43105
43315
|
function failedCases(report, printed) {
|
|
43106
43316
|
const cases = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ entry, path: suite.path })));
|
|
43107
43317
|
const read = withActualsPrinted(cases.flatMap(({ entry }) => entry.reports), printed);
|
|
@@ -43109,6 +43319,8 @@ function failedCases(report, printed) {
|
|
|
43109
43319
|
return cases.map(({ entry, path }) => {
|
|
43110
43320
|
const reports = read.slice(next, next + entry.reports.length);
|
|
43111
43321
|
next += entry.reports.length;
|
|
43322
|
+
const folded = foldedReports(reports);
|
|
43323
|
+
const shown = folded.slice(0, MOST_REPORTS_SHOWN);
|
|
43112
43324
|
return {
|
|
43113
43325
|
suite: entry.suite,
|
|
43114
43326
|
name: entry.name,
|
|
@@ -43116,11 +43328,34 @@ function failedCases(report, printed) {
|
|
|
43116
43328
|
time: entry.time,
|
|
43117
43329
|
path,
|
|
43118
43330
|
message: reports[0]?.message ?? null,
|
|
43119
|
-
detail: reports[0]?.detail ?? null,
|
|
43120
|
-
...reports.length > 1 ? { reports } : {}
|
|
43331
|
+
detail: shortened(reports[0]?.detail ?? null),
|
|
43332
|
+
...reports.length > 1 ? { reports: shown, reportCount: reports.length } : {},
|
|
43333
|
+
...folded.length > shown.length ? { reportsLeftOut: folded.length - shown.length } : {}
|
|
43121
43334
|
};
|
|
43122
43335
|
});
|
|
43123
43336
|
}
|
|
43337
|
+
function failedWithin(failed, budget) {
|
|
43338
|
+
const sizes = failed.map((entry) => JSON.stringify(entry).length);
|
|
43339
|
+
const smallestFirst = sizes.map((_, index) => index).sort((a, b) => (sizes[a] ?? 0) - (sizes[b] ?? 0));
|
|
43340
|
+
const whole = new Set;
|
|
43341
|
+
let spent = 0;
|
|
43342
|
+
for (const index of smallestFirst) {
|
|
43343
|
+
const size = sizes[index] ?? 0;
|
|
43344
|
+
if (spent + size > budget) {
|
|
43345
|
+
break;
|
|
43346
|
+
}
|
|
43347
|
+
spent += size;
|
|
43348
|
+
whole.add(index);
|
|
43349
|
+
}
|
|
43350
|
+
const kept = failed.map((entry, index) => {
|
|
43351
|
+
if (whole.has(index)) {
|
|
43352
|
+
return entry;
|
|
43353
|
+
}
|
|
43354
|
+
const { detail: _detail, reports: _reports, reportsLeftOut: _left, ...named } = entry;
|
|
43355
|
+
return { ...named, message: entry.message?.slice(0, 500) ?? null, detail: null };
|
|
43356
|
+
});
|
|
43357
|
+
return { failed: kept, briefed: failed.length - whole.size };
|
|
43358
|
+
}
|
|
43124
43359
|
function masked(expected, actual) {
|
|
43125
43360
|
const allSpelled = (value) => !PRINTED_DICTIONARY.test(value) && value.split("[").length === value.split(MASKED_BRACKET).length;
|
|
43126
43361
|
return `${expected}${actual}`.includes(MASKED_BRACKET) && allSpelled(expected) && allSpelled(actual);
|
|
@@ -43199,7 +43434,7 @@ function testProgressMessage(printed, elapsedMs) {
|
|
|
43199
43434
|
const done = finished.length === 0 ? "No suite has finished" : `${finished.length === 1 ? "One suite has" : `${finished.length} suites have`} finished${failing === 0 ? "" : `, ${failing} with failures`}`;
|
|
43200
43435
|
return `${done}${running === null ? "" : `; running ${running}`}; ${took} in.`;
|
|
43201
43436
|
}
|
|
43202
|
-
var MalformedReportError, ENTITIES, ATTRIBUTE, HOOK_FAILURE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary", SUITE_COUNTS, CASE_FAILED;
|
|
43437
|
+
var MalformedReportError, ENTITIES, ATTRIBUTE, HOOK_FAILURE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MOST_REPORTS_SHOWN = 20, LONGEST_DETAIL = 20000, MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary", SUITE_COUNTS, CASE_FAILED;
|
|
43203
43438
|
var init_junit = __esm(() => {
|
|
43204
43439
|
MalformedReportError = class MalformedReportError extends Error {
|
|
43205
43440
|
constructor(message) {
|
|
@@ -43301,25 +43536,25 @@ var DEBUG_PREFIX = "debug ";
|
|
|
43301
43536
|
var init_keeper_channel = () => {};
|
|
43302
43537
|
|
|
43303
43538
|
// src/lifecycle-log.ts
|
|
43304
|
-
import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as
|
|
43305
|
-
import { dirname as
|
|
43306
|
-
import
|
|
43539
|
+
import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync12 } from "node:fs";
|
|
43540
|
+
import { dirname as dirname20, join as join27 } from "node:path";
|
|
43541
|
+
import process11 from "node:process";
|
|
43307
43542
|
function lifecycleLogPath() {
|
|
43308
|
-
return
|
|
43543
|
+
return join27(runtimeDirectory(), "servers.log");
|
|
43309
43544
|
}
|
|
43310
43545
|
function writeLifecycle(event, fields = {}) {
|
|
43311
43546
|
const path = lifecycleLogPath();
|
|
43312
43547
|
try {
|
|
43313
|
-
mkdirSync14(
|
|
43314
|
-
if ((
|
|
43548
|
+
mkdirSync14(dirname20(path), { recursive: true });
|
|
43549
|
+
if ((statSync12(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
43315
43550
|
try {
|
|
43316
43551
|
renameSync5(path, `${path}.1`);
|
|
43317
43552
|
} catch {}
|
|
43318
43553
|
}
|
|
43319
43554
|
const line = {
|
|
43320
43555
|
at: new Date().toISOString(),
|
|
43321
|
-
pid:
|
|
43322
|
-
ppid:
|
|
43556
|
+
pid: process11.pid,
|
|
43557
|
+
ppid: process11.ppid,
|
|
43323
43558
|
version: SERVER_VERSION,
|
|
43324
43559
|
event
|
|
43325
43560
|
};
|
|
@@ -43626,14 +43861,14 @@ var init_prompts = __esm(() => {
|
|
|
43626
43861
|
|
|
43627
43862
|
// src/restart-note.ts
|
|
43628
43863
|
import { mkdirSync as mkdirSync15, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
|
|
43629
|
-
import { dirname as
|
|
43864
|
+
import { dirname as dirname21, join as join28 } from "node:path";
|
|
43630
43865
|
function restartNotePath(projectPath) {
|
|
43631
|
-
return
|
|
43866
|
+
return join28(projectPath, ".godot", "gdharness-restart.json");
|
|
43632
43867
|
}
|
|
43633
43868
|
function noteRestartBegun(note) {
|
|
43634
43869
|
const path = restartNotePath(note.projectPath);
|
|
43635
43870
|
try {
|
|
43636
|
-
mkdirSync15(
|
|
43871
|
+
mkdirSync15(dirname21(path), { recursive: true });
|
|
43637
43872
|
writeFileSync17(path, `${JSON.stringify(note, null, 2)}
|
|
43638
43873
|
`, "utf8");
|
|
43639
43874
|
} catch (error) {
|
|
@@ -43668,23 +43903,23 @@ var init_restart_note = __esm(() => {
|
|
|
43668
43903
|
|
|
43669
43904
|
// src/run-record.ts
|
|
43670
43905
|
import { createHash as createHash5 } from "node:crypto";
|
|
43671
|
-
import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as
|
|
43672
|
-
import { basename as
|
|
43906
|
+
import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync13, readFileSync as readFileSync24, realpathSync as realpathSync6, rmSync as rmSync14, statSync as statSync13 } from "node:fs";
|
|
43907
|
+
import { basename as basename9, dirname as dirname22, join as join29, resolve as resolve7 } from "node:path";
|
|
43673
43908
|
function runsDirectory() {
|
|
43674
|
-
return
|
|
43909
|
+
return join29(runtimeDirectory(), "runs");
|
|
43675
43910
|
}
|
|
43676
43911
|
function realAsFarAsItExists(path) {
|
|
43677
43912
|
const missing = [];
|
|
43678
43913
|
let at = path;
|
|
43679
43914
|
for (;; ) {
|
|
43680
43915
|
try {
|
|
43681
|
-
return
|
|
43916
|
+
return join29(realpathSync6.native(at), ...missing);
|
|
43682
43917
|
} catch {
|
|
43683
|
-
const parent =
|
|
43918
|
+
const parent = dirname22(at);
|
|
43684
43919
|
if (parent === at) {
|
|
43685
43920
|
return path;
|
|
43686
43921
|
}
|
|
43687
|
-
missing.unshift(
|
|
43922
|
+
missing.unshift(basename9(at));
|
|
43688
43923
|
at = parent;
|
|
43689
43924
|
}
|
|
43690
43925
|
}
|
|
@@ -43695,10 +43930,10 @@ function projectKey(projectPath) {
|
|
|
43695
43930
|
return createHash5("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
43696
43931
|
}
|
|
43697
43932
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
43698
|
-
return
|
|
43933
|
+
return join29(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
43699
43934
|
}
|
|
43700
43935
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
43701
|
-
return
|
|
43936
|
+
return join29(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
43702
43937
|
}
|
|
43703
43938
|
function writeLatestRun(servedBy, latest) {
|
|
43704
43939
|
try {
|
|
@@ -43723,17 +43958,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
43723
43958
|
}
|
|
43724
43959
|
function openTranscript(startedAt) {
|
|
43725
43960
|
mkdirSync16(runsDirectory(), { recursive: true });
|
|
43726
|
-
const path =
|
|
43961
|
+
const path = join29(runsDirectory(), `run-${startedAt}.log`);
|
|
43727
43962
|
return { path, fd: openSync3(path, "a") };
|
|
43728
43963
|
}
|
|
43729
43964
|
function profilePath(startedAt) {
|
|
43730
|
-
return
|
|
43965
|
+
return join29(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
43966
|
+
}
|
|
43967
|
+
function profileMarkPath(startedAt) {
|
|
43968
|
+
return join29(runsDirectory(), `run-${startedAt}.profile-mark.json`);
|
|
43731
43969
|
}
|
|
43732
43970
|
function noteIsAbout(note, playing) {
|
|
43733
43971
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
43734
43972
|
}
|
|
43735
43973
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
43736
|
-
return
|
|
43974
|
+
return join29(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
43737
43975
|
}
|
|
43738
43976
|
function writeEditorRunNote(note) {
|
|
43739
43977
|
mkdirSync16(runsDirectory(), { recursive: true });
|
|
@@ -43744,7 +43982,7 @@ function writeEditorRunNote(note) {
|
|
|
43744
43982
|
}
|
|
43745
43983
|
function readEditorRunNote(projectPath) {
|
|
43746
43984
|
for (const directory of runtimeDirectories()) {
|
|
43747
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
43985
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join29(directory, "runs", "editor-run.json"));
|
|
43748
43986
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
43749
43987
|
return note;
|
|
43750
43988
|
}
|
|
@@ -43778,7 +44016,7 @@ function readRunRecord(projectPath) {
|
|
|
43778
44016
|
if (own !== null) {
|
|
43779
44017
|
return own;
|
|
43780
44018
|
}
|
|
43781
|
-
const shared = recordAt(
|
|
44019
|
+
const shared = recordAt(join29(directory, "runs", SHARED_NOTE));
|
|
43782
44020
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
43783
44021
|
return shared;
|
|
43784
44022
|
}
|
|
@@ -43790,13 +44028,13 @@ function everyRunRecord() {
|
|
|
43790
44028
|
for (const directory of runtimeDirectories()) {
|
|
43791
44029
|
let entries;
|
|
43792
44030
|
try {
|
|
43793
|
-
entries =
|
|
44031
|
+
entries = readdirSync13(join29(directory, "runs"));
|
|
43794
44032
|
} catch {
|
|
43795
44033
|
continue;
|
|
43796
44034
|
}
|
|
43797
44035
|
for (const entry of entries) {
|
|
43798
44036
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
43799
|
-
const record = recordAt(
|
|
44037
|
+
const record = recordAt(join29(directory, "runs", entry));
|
|
43800
44038
|
if (record !== null) {
|
|
43801
44039
|
found.push(record);
|
|
43802
44040
|
}
|
|
@@ -43965,7 +44203,7 @@ function whyNotTheRun(record, running, needed) {
|
|
|
43965
44203
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
43966
44204
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
43967
44205
|
}
|
|
43968
|
-
const engine = record.command === undefined ? null :
|
|
44206
|
+
const engine = record.command === undefined ? null : basename9(record.command);
|
|
43969
44207
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
43970
44208
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
43971
44209
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -44004,7 +44242,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
44004
44242
|
}
|
|
44005
44243
|
function clearRunRecord(projectPath) {
|
|
44006
44244
|
for (const directory of runtimeDirectories()) {
|
|
44007
|
-
const shared =
|
|
44245
|
+
const shared = join29(directory, "runs", SHARED_NOTE);
|
|
44008
44246
|
const sharedRecord = recordAt(shared);
|
|
44009
44247
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
44010
44248
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -44016,7 +44254,7 @@ function clearRunRecord(projectPath) {
|
|
|
44016
44254
|
}
|
|
44017
44255
|
function ageMs(path, now) {
|
|
44018
44256
|
try {
|
|
44019
|
-
return now -
|
|
44257
|
+
return now - statSync13(path).mtimeMs;
|
|
44020
44258
|
} catch {
|
|
44021
44259
|
return 0;
|
|
44022
44260
|
}
|
|
@@ -44025,20 +44263,21 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
44025
44263
|
const directory = runsDirectory();
|
|
44026
44264
|
const kept = new Set(everyRunRecord().flatMap((record) => [
|
|
44027
44265
|
record.transcript,
|
|
44266
|
+
profileMarkPath(record.startedAt),
|
|
44028
44267
|
...record.profile === undefined ? [] : [record.profile]
|
|
44029
44268
|
]));
|
|
44030
44269
|
let entries;
|
|
44031
44270
|
try {
|
|
44032
|
-
entries =
|
|
44271
|
+
entries = readdirSync13(directory);
|
|
44033
44272
|
} catch {
|
|
44034
44273
|
return;
|
|
44035
44274
|
}
|
|
44036
44275
|
for (const entry of entries) {
|
|
44037
|
-
const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json"));
|
|
44276
|
+
const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json") || entry.endsWith(".profile-mark.json"));
|
|
44038
44277
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
44039
44278
|
continue;
|
|
44040
44279
|
}
|
|
44041
|
-
const path =
|
|
44280
|
+
const path = join29(directory, entry);
|
|
44042
44281
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
44043
44282
|
continue;
|
|
44044
44283
|
}
|
|
@@ -44059,7 +44298,7 @@ var init_run_record = __esm(() => {
|
|
|
44059
44298
|
});
|
|
44060
44299
|
|
|
44061
44300
|
// src/script-origins.ts
|
|
44062
|
-
import { join as
|
|
44301
|
+
import { join as join30 } from "node:path";
|
|
44063
44302
|
function unresolvedClassIn(message) {
|
|
44064
44303
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
44065
44304
|
}
|
|
@@ -44112,7 +44351,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
44112
44351
|
const ran = await runEngine(godotPath, [
|
|
44113
44352
|
"--headless",
|
|
44114
44353
|
"--log-file",
|
|
44115
|
-
|
|
44354
|
+
join30(logDir, "engine.log"),
|
|
44116
44355
|
"--path",
|
|
44117
44356
|
projectPath,
|
|
44118
44357
|
"--check-only",
|
|
@@ -44205,6 +44444,52 @@ function isCalibration(signature) {
|
|
|
44205
44444
|
const parts = signatureParts(signature);
|
|
44206
44445
|
return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
|
|
44207
44446
|
}
|
|
44447
|
+
function writeProfileMark(path, mark) {
|
|
44448
|
+
return writeWhole(path, JSON.stringify(mark));
|
|
44449
|
+
}
|
|
44450
|
+
function readProfileMark(path) {
|
|
44451
|
+
try {
|
|
44452
|
+
const parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
44453
|
+
return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
|
|
44454
|
+
} catch {
|
|
44455
|
+
return null;
|
|
44456
|
+
}
|
|
44457
|
+
}
|
|
44458
|
+
function totalsSince(now, mark) {
|
|
44459
|
+
if (now.frames < mark.frames || now.startedAt !== mark.startedAt) {
|
|
44460
|
+
return null;
|
|
44461
|
+
}
|
|
44462
|
+
let shrunk = 0;
|
|
44463
|
+
const functions = {};
|
|
44464
|
+
for (const [signature, sum] of Object.entries(now.functions)) {
|
|
44465
|
+
const before = mark.functions[signature];
|
|
44466
|
+
if (isCalibration(signature) || before === undefined) {
|
|
44467
|
+
functions[signature] = sum;
|
|
44468
|
+
continue;
|
|
44469
|
+
}
|
|
44470
|
+
const added = {
|
|
44471
|
+
calls: sum.calls - before.calls,
|
|
44472
|
+
selfSeconds: sum.selfSeconds - before.selfSeconds,
|
|
44473
|
+
totalSeconds: sum.totalSeconds - before.totalSeconds
|
|
44474
|
+
};
|
|
44475
|
+
if (added.calls < 0 || added.selfSeconds < 0 || added.totalSeconds < 0) {
|
|
44476
|
+
shrunk += 1;
|
|
44477
|
+
} else if (added.calls > 0) {
|
|
44478
|
+
functions[signature] = added;
|
|
44479
|
+
}
|
|
44480
|
+
}
|
|
44481
|
+
shrunk += Object.keys(mark.functions).filter((signature) => now.functions[signature] === undefined && (mark.functions[signature]?.calls ?? 0) > 0).length;
|
|
44482
|
+
return {
|
|
44483
|
+
totals: {
|
|
44484
|
+
...now,
|
|
44485
|
+
frames: now.frames - mark.frames,
|
|
44486
|
+
unreadable: Math.max(0, now.unreadable - mark.unreadable),
|
|
44487
|
+
...now.cappedFrames === undefined ? {} : { cappedFrames: Math.max(0, now.cappedFrames - (mark.cappedFrames ?? 0)) },
|
|
44488
|
+
functions
|
|
44489
|
+
},
|
|
44490
|
+
shrunk
|
|
44491
|
+
};
|
|
44492
|
+
}
|
|
44208
44493
|
function perCallCostOf(totals) {
|
|
44209
44494
|
for (const [signature, sum] of Object.entries(totals.functions)) {
|
|
44210
44495
|
const parts = signatureParts(signature);
|
|
@@ -44337,17 +44622,18 @@ import {
|
|
|
44337
44622
|
closeSync as closeSync3,
|
|
44338
44623
|
constants,
|
|
44339
44624
|
copyFileSync,
|
|
44340
|
-
existsSync as
|
|
44625
|
+
existsSync as existsSync24,
|
|
44341
44626
|
fstatSync,
|
|
44342
44627
|
mkdirSync as mkdirSync17,
|
|
44343
44628
|
openSync as openSync4,
|
|
44344
|
-
readdirSync as
|
|
44629
|
+
readdirSync as readdirSync14,
|
|
44345
44630
|
readFileSync as readFileSync26,
|
|
44346
44631
|
readSync as readSync2,
|
|
44347
44632
|
rmdirSync as rmdirSync2,
|
|
44348
|
-
statSync as
|
|
44633
|
+
statSync as statSync14,
|
|
44634
|
+
writeFileSync as writeFileSync18
|
|
44349
44635
|
} from "node:fs";
|
|
44350
|
-
import { basename as
|
|
44636
|
+
import { basename as basename10, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as join31, normalize as normalize2, relative as relative6 } from "node:path";
|
|
44351
44637
|
import { setTimeout as delay5 } from "node:timers/promises";
|
|
44352
44638
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
44353
44639
|
function editorRestartTimeoutMs() {
|
|
@@ -44377,22 +44663,22 @@ function writtenFileRefusal(path, rule) {
|
|
|
44377
44663
|
if (/^(res|user):\/\//.test(path)) {
|
|
44378
44664
|
return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
|
|
44379
44665
|
}
|
|
44380
|
-
if (!
|
|
44666
|
+
if (!isAbsolute5(path)) {
|
|
44381
44667
|
return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
|
|
44382
44668
|
}
|
|
44383
44669
|
if (!rule.extensions.includes(extname4(path).toLowerCase())) {
|
|
44384
44670
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
44385
44671
|
}
|
|
44386
|
-
const directory = realPathOr(
|
|
44387
|
-
if (!
|
|
44388
|
-
return `${named}'s directory ${
|
|
44672
|
+
const directory = realPathOr(dirname23(path));
|
|
44673
|
+
if (!existsSync24(directory)) {
|
|
44674
|
+
return `${named}'s directory ${dirname23(path)} does not exist; ${what} creates no directories.`;
|
|
44389
44675
|
}
|
|
44390
|
-
const where =
|
|
44676
|
+
const where = join31(directory, basename10(path));
|
|
44391
44677
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
44392
44678
|
if (inside !== undefined) {
|
|
44393
44679
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
44394
44680
|
}
|
|
44395
|
-
const taken = rule.writes(where).find((file) =>
|
|
44681
|
+
const taken = rule.writes(where).find((file) => existsSync24(file));
|
|
44396
44682
|
if (taken !== undefined) {
|
|
44397
44683
|
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.`;
|
|
44398
44684
|
}
|
|
@@ -44501,7 +44787,7 @@ function exited(run) {
|
|
|
44501
44787
|
return run.exitCode !== null || run.exitSignal !== null;
|
|
44502
44788
|
}
|
|
44503
44789
|
function howItExited(run) {
|
|
44504
|
-
return run.exitSignal === null ?
|
|
44790
|
+
return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}, with no exit code`;
|
|
44505
44791
|
}
|
|
44506
44792
|
function errorsUnread(run) {
|
|
44507
44793
|
return run.throughEditor && typeof run.errorReport !== "string";
|
|
@@ -44609,7 +44895,7 @@ function endedWithoutACode(run) {
|
|
|
44609
44895
|
function ourRunEnded(run, now) {
|
|
44610
44896
|
const which = `${run.throughEditor ? "played in the editor" : "started"}${run.pid === null ? "" : ` (pid ${run.pid})`}`;
|
|
44611
44897
|
const when = run.endedAt === undefined ? "" : ` ${Math.max(0, Math.round((now - run.endedAt) / 1000))}s ago`;
|
|
44612
|
-
const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with
|
|
44898
|
+
const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with ${exitCodeText(run.exitCode)}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
|
|
44613
44899
|
return `No game with the runtime addon is running: the game this server ${which} ended${when}, and ${how}. editor_output has what it printed, and editor_run start runs it again.`;
|
|
44614
44900
|
}
|
|
44615
44901
|
function stopVerdict(stop) {
|
|
@@ -44928,7 +45214,7 @@ function notRunNote(notRun, failFast) {
|
|
|
44928
45214
|
function exportPresetRefusal(projectPath, preset) {
|
|
44929
45215
|
let text;
|
|
44930
45216
|
try {
|
|
44931
|
-
text = readFileSync26(
|
|
45217
|
+
text = readFileSync26(join31(projectPath, "export_presets.cfg"), "utf8");
|
|
44932
45218
|
} catch {
|
|
44933
45219
|
return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. project_export add_preset writes one, as the editor's Project > Export does.`;
|
|
44934
45220
|
}
|
|
@@ -44950,7 +45236,7 @@ function exportPresetRefusal(projectPath, preset) {
|
|
|
44950
45236
|
}
|
|
44951
45237
|
return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. project_export add_preset adds one.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
|
|
44952
45238
|
}
|
|
44953
|
-
function exportAnswer(ending, asked, before, after, beside = []) {
|
|
45239
|
+
function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null) {
|
|
44954
45240
|
const { log } = ending;
|
|
44955
45241
|
const written = after !== null && after !== before;
|
|
44956
45242
|
const notCopied = librariesNotCopied(log.all);
|
|
@@ -44967,6 +45253,7 @@ function exportAnswer(ending, asked, before, after, beside = []) {
|
|
|
44967
45253
|
librariesNotLoaded: notCopied,
|
|
44968
45254
|
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.`
|
|
44969
45255
|
},
|
|
45256
|
+
...docCacheNote === null ? {} : { docCacheNote },
|
|
44970
45257
|
...foldedEntriesAnswer(log)
|
|
44971
45258
|
};
|
|
44972
45259
|
if (verdict.exported) {
|
|
@@ -44991,9 +45278,9 @@ function writtenBeside(before, after, output, within) {
|
|
|
44991
45278
|
function filesIn(folder) {
|
|
44992
45279
|
const found = new Map;
|
|
44993
45280
|
try {
|
|
44994
|
-
for (const entry of
|
|
45281
|
+
for (const entry of readdirSync14(folder, { withFileTypes: true })) {
|
|
44995
45282
|
if (entry.isFile()) {
|
|
44996
|
-
found.set(entry.name,
|
|
45283
|
+
found.set(entry.name, statSync14(join31(folder, entry.name)).mtimeMs);
|
|
44997
45284
|
}
|
|
44998
45285
|
}
|
|
44999
45286
|
} catch {}
|
|
@@ -45023,7 +45310,7 @@ function unsavedInEditorNote(scripts) {
|
|
|
45023
45310
|
return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
|
|
45024
45311
|
}
|
|
45025
45312
|
function editorAddonMissing(projectPath) {
|
|
45026
|
-
if (!
|
|
45313
|
+
if (!existsSync24(join31(projectPath, "project.godot"))) {
|
|
45027
45314
|
return null;
|
|
45028
45315
|
}
|
|
45029
45316
|
const report = inspectProject(projectPath);
|
|
@@ -45261,7 +45548,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
45261
45548
|
let projectGodot = null;
|
|
45262
45549
|
if (projectPath !== null) {
|
|
45263
45550
|
try {
|
|
45264
|
-
projectGodot = readFileSync26(
|
|
45551
|
+
projectGodot = readFileSync26(join31(projectPath, "project.godot"), "utf8");
|
|
45265
45552
|
} catch {}
|
|
45266
45553
|
}
|
|
45267
45554
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -45435,7 +45722,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
45435
45722
|
}
|
|
45436
45723
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
45437
45724
|
try {
|
|
45438
|
-
return settingKeys(readFileSync26(
|
|
45725
|
+
return settingKeys(readFileSync26(join31(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
45439
45726
|
} catch {
|
|
45440
45727
|
return true;
|
|
45441
45728
|
}
|
|
@@ -45450,13 +45737,13 @@ function uncaughtNote(uncaught) {
|
|
|
45450
45737
|
}
|
|
45451
45738
|
function elsewhereIn(projectPath, asked) {
|
|
45452
45739
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
45453
|
-
if (wanted === "" ||
|
|
45740
|
+
if (wanted === "" || existsSync24(join31(projectPath, wanted))) {
|
|
45454
45741
|
return "";
|
|
45455
45742
|
}
|
|
45456
45743
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
45457
45744
|
let near = [];
|
|
45458
45745
|
try {
|
|
45459
|
-
near =
|
|
45746
|
+
near = readdirSync14(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()));
|
|
45460
45747
|
} catch {
|
|
45461
45748
|
return "";
|
|
45462
45749
|
}
|
|
@@ -45547,23 +45834,23 @@ function engineChecked(why) {
|
|
|
45547
45834
|
}
|
|
45548
45835
|
function isDirectory(path) {
|
|
45549
45836
|
try {
|
|
45550
|
-
return
|
|
45837
|
+
return statSync14(path).isDirectory();
|
|
45551
45838
|
} catch {
|
|
45552
45839
|
return false;
|
|
45553
45840
|
}
|
|
45554
45841
|
}
|
|
45555
45842
|
function scriptsUnder(directory, prefix) {
|
|
45556
|
-
if (
|
|
45843
|
+
if (existsSync24(join31(directory, ".gdignore"))) {
|
|
45557
45844
|
return [];
|
|
45558
45845
|
}
|
|
45559
45846
|
const found = [];
|
|
45560
|
-
for (const entry of
|
|
45847
|
+
for (const entry of readdirSync14(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
45561
45848
|
if (entry.name.startsWith(".")) {
|
|
45562
45849
|
continue;
|
|
45563
45850
|
}
|
|
45564
45851
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
45565
45852
|
if (entry.isDirectory()) {
|
|
45566
|
-
found.push(...scriptsUnder(
|
|
45853
|
+
found.push(...scriptsUnder(join31(directory, entry.name), path));
|
|
45567
45854
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
45568
45855
|
found.push(path);
|
|
45569
45856
|
}
|
|
@@ -45579,7 +45866,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
45579
45866
|
return false;
|
|
45580
45867
|
}
|
|
45581
45868
|
try {
|
|
45582
|
-
return readFileSync26(
|
|
45869
|
+
return readFileSync26(join31(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
45583
45870
|
} catch {
|
|
45584
45871
|
return false;
|
|
45585
45872
|
}
|
|
@@ -45604,7 +45891,7 @@ function needsItsOwnProcess(asked) {
|
|
|
45604
45891
|
class GodotServer {
|
|
45605
45892
|
stdio = null;
|
|
45606
45893
|
locator = new GodotLocator;
|
|
45607
|
-
operationsScript =
|
|
45894
|
+
operationsScript = join31(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
45608
45895
|
godotBridge;
|
|
45609
45896
|
tools = buildToolDefinitions();
|
|
45610
45897
|
activeProcess = null;
|
|
@@ -45631,6 +45918,7 @@ class GodotServer {
|
|
|
45631
45918
|
inFlight = new Map;
|
|
45632
45919
|
callsOnGames = new Set;
|
|
45633
45920
|
callsTaken = 0;
|
|
45921
|
+
answersDirectory;
|
|
45634
45922
|
clientNoted = null;
|
|
45635
45923
|
parentCommand = null;
|
|
45636
45924
|
endNoted = false;
|
|
@@ -45864,14 +46152,14 @@ class GodotServer {
|
|
|
45864
46152
|
}
|
|
45865
46153
|
noteTheStart() {
|
|
45866
46154
|
const cwd = process.cwd();
|
|
45867
|
-
const mcpJson =
|
|
46155
|
+
const mcpJson = join31(cwd, ".mcp.json");
|
|
45868
46156
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
45869
46157
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
45870
46158
|
writeLifecycle("started", {
|
|
45871
46159
|
argv: process.argv.slice(1),
|
|
45872
46160
|
cwd,
|
|
45873
46161
|
project: this.ownProject,
|
|
45874
|
-
mcpJson:
|
|
46162
|
+
mcpJson: existsSync24(mcpJson) ? mcpJson : null
|
|
45875
46163
|
});
|
|
45876
46164
|
if (predecessor !== null) {
|
|
45877
46165
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -46012,7 +46300,7 @@ class GodotServer {
|
|
|
46012
46300
|
return checked.response;
|
|
46013
46301
|
}
|
|
46014
46302
|
const named = args["projectPath"];
|
|
46015
|
-
if (typeof named === "string" &&
|
|
46303
|
+
if (typeof named === "string" && existsSync24(join31(named, "project.godot"))) {
|
|
46016
46304
|
this.lastProjectPath = named;
|
|
46017
46305
|
}
|
|
46018
46306
|
this.updates.refresh();
|
|
@@ -46029,12 +46317,66 @@ class GodotServer {
|
|
|
46029
46317
|
this.inFlight.set(call, { tool: spec.name, op: checked.op ?? "", startedAt: Date.now() });
|
|
46030
46318
|
try {
|
|
46031
46319
|
const answer = await withCallSignal(ctx.mcpReq.signal, () => this.answered(spec.name, checked.op ?? "", args), progress);
|
|
46032
|
-
return this.withFeedbackNotice(this.withUpdateNotice(answer));
|
|
46320
|
+
return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(answer)));
|
|
46033
46321
|
} finally {
|
|
46034
46322
|
this.inFlight.delete(call);
|
|
46035
46323
|
}
|
|
46036
46324
|
});
|
|
46037
46325
|
}
|
|
46326
|
+
withinTheClientsLimit(tool, answer) {
|
|
46327
|
+
const sizeOf = (item) => item.type === "text" ? Buffer.byteLength(item.text) : item.data.length;
|
|
46328
|
+
const bytes = answer.content.reduce((sum, item) => sum + sizeOf(item), 0);
|
|
46329
|
+
if (bytes <= ANSWER_BUDGET) {
|
|
46330
|
+
return answer;
|
|
46331
|
+
}
|
|
46332
|
+
this.answersDirectory ??= scratchDirectory("answers");
|
|
46333
|
+
const directory = this.answersDirectory;
|
|
46334
|
+
const stem = `${tool}-${Date.now()}`;
|
|
46335
|
+
let savedAt;
|
|
46336
|
+
let notSaved;
|
|
46337
|
+
try {
|
|
46338
|
+
savedAt = answer.content.map((item, index) => {
|
|
46339
|
+
const file = join31(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
|
|
46340
|
+
writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
|
|
46341
|
+
return file;
|
|
46342
|
+
});
|
|
46343
|
+
} catch (error) {
|
|
46344
|
+
notSaved = errorMessage(error);
|
|
46345
|
+
}
|
|
46346
|
+
const first = answer.content[0];
|
|
46347
|
+
let parsed = null;
|
|
46348
|
+
try {
|
|
46349
|
+
parsed = first?.type === "text" ? JSON.parse(first.text) : null;
|
|
46350
|
+
} catch {}
|
|
46351
|
+
const small = {};
|
|
46352
|
+
const leftOut = [];
|
|
46353
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
46354
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
46355
|
+
if (JSON.stringify(value).length <= 2000) {
|
|
46356
|
+
small[key] = value;
|
|
46357
|
+
} else {
|
|
46358
|
+
leftOut.push(key);
|
|
46359
|
+
}
|
|
46360
|
+
}
|
|
46361
|
+
}
|
|
46362
|
+
return {
|
|
46363
|
+
...answer,
|
|
46364
|
+
content: [
|
|
46365
|
+
{
|
|
46366
|
+
type: "text",
|
|
46367
|
+
text: answerJson({
|
|
46368
|
+
...small,
|
|
46369
|
+
answerTooLarge: {
|
|
46370
|
+
bytes,
|
|
46371
|
+
savedAt,
|
|
46372
|
+
leftOut: leftOut.length > 0 ? leftOut : undefined,
|
|
46373
|
+
note: savedAt === undefined ? `The answer was ${bytes} bytes, past the ${ANSWER_BUDGET} this server sends in one message (Claude Code closes the connection past 16 MB), and writing it to a file failed: ${notSaved ?? "no reason given"}. The fields of its top level that are short are kept here.` : `The answer was ${bytes} bytes, past the ${ANSWER_BUDGET} this server sends in one message (Claude Code closes the connection past 16 MB), so it was written whole to savedAt; read it from there. The fields of its top level that are short are kept here. The files are removed once this server has stopped.`
|
|
46374
|
+
}
|
|
46375
|
+
})
|
|
46376
|
+
}
|
|
46377
|
+
]
|
|
46378
|
+
};
|
|
46379
|
+
}
|
|
46038
46380
|
async answered(tool, op, args) {
|
|
46039
46381
|
try {
|
|
46040
46382
|
return await this.dispatch(tool, op, args);
|
|
@@ -46286,6 +46628,7 @@ class GodotServer {
|
|
|
46286
46628
|
const contained = this.containProjectFiles({ ...arguments_, ...extra });
|
|
46287
46629
|
return contained.ok ? await this.handleViaBridge(command, contained.value, op === "" ? tool : `${tool} ${op}`) : contained.response;
|
|
46288
46630
|
};
|
|
46631
|
+
const editorOrEngine = async (command, effect, extra = {}) => await this.editorOrEngine(command, { ...arguments_, ...extra }, `${tool} ${op}`, effect);
|
|
46289
46632
|
switch (tool) {
|
|
46290
46633
|
case "project_info":
|
|
46291
46634
|
return await this.handleProjectInfo(args);
|
|
@@ -46298,7 +46641,7 @@ class GodotServer {
|
|
|
46298
46641
|
case "project_test":
|
|
46299
46642
|
return await this.handleRunTests(args);
|
|
46300
46643
|
case "scene_create":
|
|
46301
|
-
return op === "create" ? await
|
|
46644
|
+
return op === "create" ? await editorOrEngine("create_scene", "writes") : await editorOrEngine("save_scene", "writes");
|
|
46302
46645
|
case "scene_tree":
|
|
46303
46646
|
return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
|
|
46304
46647
|
scenePath: file,
|
|
@@ -46308,28 +46651,28 @@ class GodotServer {
|
|
|
46308
46651
|
case "scene_node":
|
|
46309
46652
|
switch (op) {
|
|
46310
46653
|
case "add":
|
|
46311
|
-
return await
|
|
46654
|
+
return await editorOrEngine("add_node", "writes");
|
|
46312
46655
|
case "get":
|
|
46313
|
-
return await
|
|
46656
|
+
return await editorOrEngine("get_node_properties", "reads");
|
|
46314
46657
|
case "set":
|
|
46315
|
-
return await
|
|
46658
|
+
return await editorOrEngine("set_node_properties", "writes");
|
|
46316
46659
|
case "duplicate":
|
|
46317
|
-
return await
|
|
46660
|
+
return await editorOrEngine("duplicate_node", "writes", { parentPath: args["parentNodePath"] });
|
|
46318
46661
|
case "reparent":
|
|
46319
|
-
return await
|
|
46662
|
+
return await editorOrEngine("reparent_node", "writes");
|
|
46320
46663
|
case "delete":
|
|
46321
|
-
return await
|
|
46664
|
+
return await editorOrEngine("delete_node", "writes");
|
|
46322
46665
|
default:
|
|
46323
|
-
return await
|
|
46666
|
+
return await editorOrEngine("set_tilemap_cells", "writes", { tilemapNodePath: args["nodePath"] });
|
|
46324
46667
|
}
|
|
46325
46668
|
case "scene_signal":
|
|
46326
46669
|
switch (op) {
|
|
46327
46670
|
case "connect":
|
|
46328
|
-
return await
|
|
46671
|
+
return await editorOrEngine("connect_signal", "writes");
|
|
46329
46672
|
case "disconnect":
|
|
46330
|
-
return await
|
|
46673
|
+
return await editorOrEngine("disconnect_signal", "writes");
|
|
46331
46674
|
default:
|
|
46332
|
-
return await
|
|
46675
|
+
return await editorOrEngine("list_connections", "reads");
|
|
46333
46676
|
}
|
|
46334
46677
|
case "scene_animation":
|
|
46335
46678
|
switch (op) {
|
|
@@ -46380,17 +46723,17 @@ class GodotServer {
|
|
|
46380
46723
|
}), async () => await bridge("read_resource", kept));
|
|
46381
46724
|
}
|
|
46382
46725
|
case "create":
|
|
46383
|
-
return await
|
|
46726
|
+
return await editorOrEngine("create_resource", "writes");
|
|
46384
46727
|
case "modify":
|
|
46385
|
-
return await
|
|
46728
|
+
return await editorOrEngine("modify_resource", "writes");
|
|
46386
46729
|
case "create_shader":
|
|
46387
|
-
return await
|
|
46730
|
+
return await editorOrEngine("create_shader", "writes", { shaderPath: args["resourcePath"] });
|
|
46388
46731
|
case "create_tileset":
|
|
46389
|
-
return await
|
|
46732
|
+
return await editorOrEngine("create_tileset", "writes", { tilesetPath: args["resourcePath"] });
|
|
46390
46733
|
case "set_theme_color":
|
|
46391
|
-
return await
|
|
46734
|
+
return await editorOrEngine("set_theme_color", "writes", { themePath: args["resourcePath"] });
|
|
46392
46735
|
default:
|
|
46393
|
-
return await
|
|
46736
|
+
return await editorOrEngine("set_theme_font_size", "writes", { themePath: args["resourcePath"] });
|
|
46394
46737
|
}
|
|
46395
46738
|
case "editor_launch":
|
|
46396
46739
|
if (op === "close") {
|
|
@@ -46611,8 +46954,8 @@ class GodotServer {
|
|
|
46611
46954
|
if (path === undefined) {
|
|
46612
46955
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
46613
46956
|
}
|
|
46614
|
-
const file =
|
|
46615
|
-
if (!
|
|
46957
|
+
const file = join31(path, "project.godot");
|
|
46958
|
+
if (!existsSync24(file)) {
|
|
46616
46959
|
return {
|
|
46617
46960
|
ok: false,
|
|
46618
46961
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -46658,7 +47001,7 @@ class GodotServer {
|
|
|
46658
47001
|
}
|
|
46659
47002
|
runEnvironment(args) {
|
|
46660
47003
|
const savesIn = readNonEmptyString(args, "savesIn");
|
|
46661
|
-
if (savesIn !== undefined && !
|
|
47004
|
+
if (savesIn !== undefined && !isAbsolute5(savesIn)) {
|
|
46662
47005
|
return {
|
|
46663
47006
|
ok: false,
|
|
46664
47007
|
response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
|
|
@@ -46917,7 +47260,8 @@ class GodotServer {
|
|
|
46917
47260
|
uidsCreated: given,
|
|
46918
47261
|
stillWithoutUid: after,
|
|
46919
47262
|
note: uidsLeftNote(after.length),
|
|
46920
|
-
...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
|
|
47263
|
+
...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
|
|
47264
|
+
...imported.docCacheNote === undefined ? {} : { docCacheNote: imported.docCacheNote }
|
|
46921
47265
|
}
|
|
46922
47266
|
}, scanned));
|
|
46923
47267
|
}
|
|
@@ -47156,6 +47500,9 @@ class GodotServer {
|
|
|
47156
47500
|
if (imported.extensionNote !== undefined) {
|
|
47157
47501
|
extra["extensionNote"] = imported.extensionNote;
|
|
47158
47502
|
}
|
|
47503
|
+
if (imported.docCacheNote !== undefined) {
|
|
47504
|
+
extra["docCacheNote"] = imported.docCacheNote;
|
|
47505
|
+
}
|
|
47159
47506
|
}
|
|
47160
47507
|
const after = await this.operation("get_import_status", statusAsk, projectPath);
|
|
47161
47508
|
if (!after.ok) {
|
|
@@ -47256,6 +47603,10 @@ class GodotServer {
|
|
|
47256
47603
|
if (unknown.length > 0) {
|
|
47257
47604
|
return this.createErrorResponse(`project_info cannot include ${unknown.join(", ")}. Sections: ${Object.keys(PROJECT_INFO_SECTIONS2).join(", ")}.`);
|
|
47258
47605
|
}
|
|
47606
|
+
const forValidation = ["preset", "detail"].filter((name) => args[name] !== undefined);
|
|
47607
|
+
if (forValidation.length > 0 && !include.includes("validation")) {
|
|
47608
|
+
return this.createErrorResponse(`${forValidation.join(" and ")} ${forValidation.length === 1 ? "is" : "are"} read only by the validation section, which this call does not include: add "validation" to include, or leave ${forValidation.length === 1 ? "it" : "them"} out.`);
|
|
47609
|
+
}
|
|
47259
47610
|
const project = this.project(args);
|
|
47260
47611
|
if (!project.ok) {
|
|
47261
47612
|
return project.response;
|
|
@@ -47268,7 +47619,7 @@ class GodotServer {
|
|
|
47268
47619
|
const name = application["config/name"];
|
|
47269
47620
|
const mainScene = application["run/main_scene"];
|
|
47270
47621
|
const info = {
|
|
47271
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
47622
|
+
name: typeof name === "string" && name !== "" ? name : basename10(project.value.path),
|
|
47272
47623
|
path: project.value.path,
|
|
47273
47624
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
47274
47625
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -47351,18 +47702,18 @@ class GodotServer {
|
|
|
47351
47702
|
if (!engine.ok) {
|
|
47352
47703
|
return engine.response;
|
|
47353
47704
|
}
|
|
47354
|
-
const folder =
|
|
47705
|
+
const folder = dirname23(output.absolutePath);
|
|
47355
47706
|
try {
|
|
47356
47707
|
mkdirSync17(folder, { recursive: true });
|
|
47357
47708
|
} catch (error) {
|
|
47358
|
-
return this.createErrorResponse(`Cannot create ${
|
|
47709
|
+
return this.createErrorResponse(`Cannot create ${dirname23(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
47359
47710
|
}
|
|
47360
47711
|
const debug = readBoolean(args, "debug") ?? false;
|
|
47361
47712
|
const exportLogs = scratchDirectory("export");
|
|
47362
47713
|
const exportArgs = [
|
|
47363
47714
|
"--headless",
|
|
47364
47715
|
"--log-file",
|
|
47365
|
-
|
|
47716
|
+
join31(exportLogs, "engine.log"),
|
|
47366
47717
|
"--path",
|
|
47367
47718
|
project.value.path,
|
|
47368
47719
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -47370,24 +47721,37 @@ class GodotServer {
|
|
|
47370
47721
|
output.absolutePath
|
|
47371
47722
|
];
|
|
47372
47723
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
47373
|
-
const writtenAt = () =>
|
|
47374
|
-
const
|
|
47375
|
-
const
|
|
47376
|
-
|
|
47377
|
-
|
|
47378
|
-
|
|
47379
|
-
|
|
47380
|
-
|
|
47724
|
+
const writtenAt = () => existsSync24(output.absolutePath) ? statSync14(output.absolutePath).mtimeMs : null;
|
|
47725
|
+
const caches = editorCacheDirectory(engine.value);
|
|
47726
|
+
const exportOnce = async () => {
|
|
47727
|
+
const before = writtenAt();
|
|
47728
|
+
const listedBefore = filesIn(folder);
|
|
47729
|
+
const cachesBefore = docCachesWritten(caches);
|
|
47730
|
+
const freed = freeLibraryCopies(project.value.path);
|
|
47731
|
+
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
47732
|
+
this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
|
|
47733
|
+
}
|
|
47734
|
+
const ending = await runEngine(engine.value, exportArgs, {
|
|
47735
|
+
timeout: { ms: 300000, said: "five minutes" }
|
|
47736
|
+
});
|
|
47737
|
+
const crash = docCacheCrash(ending, cachesBefore, docCachesWritten(caches), "export");
|
|
47738
|
+
return { ending, before, listedBefore, crash };
|
|
47739
|
+
};
|
|
47740
|
+
let run;
|
|
47741
|
+
let docCacheNote;
|
|
47381
47742
|
try {
|
|
47382
|
-
|
|
47743
|
+
const first = await exportOnce();
|
|
47744
|
+
docCacheNote = first.crash;
|
|
47745
|
+
run = first.crash === null ? first : await exportOnce();
|
|
47383
47746
|
} finally {
|
|
47384
47747
|
discard(exportLogs);
|
|
47385
47748
|
}
|
|
47749
|
+
const { ending, before, listedBefore } = run;
|
|
47386
47750
|
if (ending.exitSignal === null && ending.exitCode === null) {
|
|
47387
47751
|
return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
|
|
47388
47752
|
}
|
|
47389
|
-
const beside = writtenBeside(listedBefore, filesIn(folder),
|
|
47390
|
-
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside);
|
|
47753
|
+
const beside = writtenBeside(listedBefore, filesIn(folder), basename10(output.absolutePath), dirname23(output.relativePath).replaceAll("\\", "/"));
|
|
47754
|
+
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
|
|
47391
47755
|
}
|
|
47392
47756
|
reportTestProgress(log) {
|
|
47393
47757
|
const progress = callProgress();
|
|
@@ -47440,10 +47804,10 @@ class GodotServer {
|
|
|
47440
47804
|
if (movie !== null) {
|
|
47441
47805
|
return this.createErrorResponse(movie);
|
|
47442
47806
|
}
|
|
47443
|
-
const missing = paths.value.filter((path) => !
|
|
47807
|
+
const missing = paths.value.filter((path) => !existsSync24(join31(project.value.path, path.slice("res://".length))));
|
|
47444
47808
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
47445
47809
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
47446
|
-
const noRunner = !
|
|
47810
|
+
const noRunner = !existsSync24(join31(project.value.path, runner));
|
|
47447
47811
|
if (asked.length === 0) {
|
|
47448
47812
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
47449
47813
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -47534,21 +47898,21 @@ class GodotServer {
|
|
|
47534
47898
|
await untilGone([run.process.pid]);
|
|
47535
47899
|
}
|
|
47536
47900
|
await run.settled;
|
|
47537
|
-
const reportsDir =
|
|
47901
|
+
const reportsDir = join31(project.value.path, ".godot", ...ours.split("/"));
|
|
47538
47902
|
let report = null;
|
|
47539
47903
|
let reportProblem = null;
|
|
47540
47904
|
try {
|
|
47541
|
-
const written =
|
|
47905
|
+
const written = existsSync24(reportsDir) ? readdirSync14(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
47542
47906
|
const newest = written.at(-1);
|
|
47543
47907
|
if (newest !== undefined) {
|
|
47544
|
-
report = parseJUnit(readFileSync26(
|
|
47908
|
+
report = parseJUnit(readFileSync26(join31(reportsDir, newest, "results.xml"), "utf8"));
|
|
47545
47909
|
}
|
|
47546
47910
|
} catch (error) {
|
|
47547
47911
|
reportProblem = errorMessage(error);
|
|
47548
47912
|
} finally {
|
|
47549
47913
|
discard(reportsDir, userData);
|
|
47550
47914
|
try {
|
|
47551
|
-
rmdirSync2(
|
|
47915
|
+
rmdirSync2(dirname23(reportsDir));
|
|
47552
47916
|
} catch {}
|
|
47553
47917
|
}
|
|
47554
47918
|
const engineEntries = foldedRepeats(run.log.select({
|
|
@@ -47580,7 +47944,7 @@ class GodotServer {
|
|
|
47580
47944
|
const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
|
|
47581
47945
|
const scriptErrors = [...declaring.origins, ...printedErrors];
|
|
47582
47946
|
const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
|
|
47583
|
-
const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ??
|
|
47947
|
+
const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? howItExited(run);
|
|
47584
47948
|
const nothingAt = missing.join(" and ");
|
|
47585
47949
|
const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
|
|
47586
47950
|
const missingNote = missing.length === 0 ? undefined : `Nothing at ${nothingAt}, so ${missing.length === 1 ? "it was" : "they were"} left out and only ${asked.join(", ")} ran; a run of fewer paths than were asked for is not called a pass.${elsewhereIn(project.value.path, missing[0] ?? "")}`;
|
|
@@ -47640,7 +48004,7 @@ class GodotServer {
|
|
|
47640
48004
|
isError: true
|
|
47641
48005
|
};
|
|
47642
48006
|
}
|
|
47643
|
-
const failed = failedCases(report, Buffer.concat(console2).toString("utf8"));
|
|
48007
|
+
const { failed, briefed } = failedWithin(failedCases(report, Buffer.concat(console2).toString("utf8")), FAILED_BUDGET);
|
|
47644
48008
|
const orphans = orphansPrinted(said);
|
|
47645
48009
|
const leftBehind = new Map(orphans.suites.map((suite) => [suite.path, suite.orphans]));
|
|
47646
48010
|
const orphansIn = (path) => path === null ? 0 : leftBehind.get(path) ?? 0;
|
|
@@ -47680,6 +48044,7 @@ class GodotServer {
|
|
|
47680
48044
|
skipped: report.skipped,
|
|
47681
48045
|
time: report.time,
|
|
47682
48046
|
failed,
|
|
48047
|
+
failedWithoutDetail: briefed > 0 ? briefed : undefined,
|
|
47683
48048
|
hookFailures: hookFailures.length > 0 ? hookFailures : undefined,
|
|
47684
48049
|
uncaughtScriptErrors: uncaughtFolded.length > 0 ? uncaughtFolded : undefined,
|
|
47685
48050
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
@@ -47691,6 +48056,7 @@ class GodotServer {
|
|
|
47691
48056
|
...scanned,
|
|
47692
48057
|
note: [
|
|
47693
48058
|
missingNote,
|
|
48059
|
+
briefed > 0 ? `${briefed} of the ${failed.length} failing cases, those with the largest reports, are named with their first message only, since their reports would have made the answer too large to deliver; pass one case's suite path to see its reports.` : undefined,
|
|
47694
48060
|
notApplied.length > 0 ? "An ignore entry named a case that ran all the same: gdUnit4 leaves a case out only as suite_name:test_name, and reads an entry with no colon as a suite name or a piece of a suite path. ignoreNotApplied gives the form for each case that ran." : undefined,
|
|
47695
48061
|
uncaughtNote(uncaught),
|
|
47696
48062
|
ranAs.startsWith("warnings") && warnings.length === 0 ? "gdUnit4 exits 101 for orphan nodes when nothing failed, and this run printed no count of them: orphan reporting may be off in the project settings." : notRunNote(notRun, readBoolean(args, "failFast") === true)
|
|
@@ -47854,7 +48220,11 @@ class GodotServer {
|
|
|
47854
48220
|
};
|
|
47855
48221
|
}
|
|
47856
48222
|
markedIfStale(answer) {
|
|
47857
|
-
|
|
48223
|
+
const status = this.godotBridge.getStatus();
|
|
48224
|
+
if (!hasSaidWhoItIs(status)) {
|
|
48225
|
+
return answer;
|
|
48226
|
+
}
|
|
48227
|
+
return markIfStale(answer, status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
|
|
47858
48228
|
}
|
|
47859
48229
|
async diagnosticsOf(args) {
|
|
47860
48230
|
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
@@ -47901,7 +48271,7 @@ class GodotServer {
|
|
|
47901
48271
|
const cached = cachedClasses(projectPath);
|
|
47902
48272
|
const sourceOf = (resourcePath) => {
|
|
47903
48273
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
47904
|
-
if (!contained.ok || !
|
|
48274
|
+
if (!contained.ok || !existsSync24(contained.absolutePath)) {
|
|
47905
48275
|
return null;
|
|
47906
48276
|
}
|
|
47907
48277
|
try {
|
|
@@ -47995,7 +48365,7 @@ class GodotServer {
|
|
|
47995
48365
|
}
|
|
47996
48366
|
sourceOfClass(projectPath, resourcePath) {
|
|
47997
48367
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
47998
|
-
if (!contained.ok || !
|
|
48368
|
+
if (!contained.ok || !existsSync24(contained.absolutePath)) {
|
|
47999
48369
|
return null;
|
|
48000
48370
|
}
|
|
48001
48371
|
try {
|
|
@@ -48157,7 +48527,7 @@ class GodotServer {
|
|
|
48157
48527
|
...Array.isArray(stack) ? {
|
|
48158
48528
|
stack: stack.map((frame) => ({
|
|
48159
48529
|
...frame,
|
|
48160
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
48530
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join31(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
48161
48531
|
}))
|
|
48162
48532
|
} : {}
|
|
48163
48533
|
});
|
|
@@ -48436,7 +48806,7 @@ class GodotServer {
|
|
|
48436
48806
|
return null;
|
|
48437
48807
|
}
|
|
48438
48808
|
try {
|
|
48439
|
-
return settingKeys(readFileSync26(
|
|
48809
|
+
return settingKeys(readFileSync26(join31(projectPath, "project.godot"), "utf8"));
|
|
48440
48810
|
} catch {
|
|
48441
48811
|
return null;
|
|
48442
48812
|
}
|
|
@@ -48675,7 +49045,7 @@ class GodotServer {
|
|
|
48675
49045
|
return;
|
|
48676
49046
|
}
|
|
48677
49047
|
const connected = this.godotBridge.isConnected() ? this.godotBridge.getStatus().editorPid : undefined;
|
|
48678
|
-
const pids = editorsOfProjectIn(tree, projectPath).filter((pid) => pid !== connected);
|
|
49048
|
+
const pids = editorsOfProjectIn(tree, projectPath).filter((pid) => pid !== connected && alive(pid));
|
|
48679
49049
|
const started = await startTimesOf(pids);
|
|
48680
49050
|
return pids.map((pid) => {
|
|
48681
49051
|
const at = started.get(pid) ?? tree.get(pid)?.startedAt;
|
|
@@ -48873,7 +49243,7 @@ class GodotServer {
|
|
|
48873
49243
|
}
|
|
48874
49244
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
48875
49245
|
const budgetMs = budget.ms;
|
|
48876
|
-
if (!
|
|
49246
|
+
if (!existsSync24(join31(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
48877
49247
|
return runtimeVerdict(null, {
|
|
48878
49248
|
addon: false,
|
|
48879
49249
|
budgetMs,
|
|
@@ -48959,7 +49329,7 @@ class GodotServer {
|
|
|
48959
49329
|
}
|
|
48960
49330
|
let announcedAt;
|
|
48961
49331
|
try {
|
|
48962
|
-
announcedAt =
|
|
49332
|
+
announcedAt = statSync14(endpoint.file).mtimeMs;
|
|
48963
49333
|
} catch {
|
|
48964
49334
|
return;
|
|
48965
49335
|
}
|
|
@@ -49849,9 +50219,13 @@ class GodotServer {
|
|
|
49849
50219
|
if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
|
|
49850
50220
|
return this.createErrorResponse(`callersOf takes a function's name, or Class.name for the functions of one class, and ${JSON.stringify(callersName)} is neither.`, ["editor_run profile names each function as it answers; pass one of those"]);
|
|
49851
50221
|
}
|
|
50222
|
+
const asked = {
|
|
50223
|
+
mark: readBoolean(args, "mark") === true,
|
|
50224
|
+
since: readString(args, "since") === "mark"
|
|
50225
|
+
};
|
|
49852
50226
|
const going = await this.runStillGoing(run);
|
|
49853
50227
|
const projectPath = run.projectPath;
|
|
49854
|
-
const projectGone = projectPath !== null && !
|
|
50228
|
+
const projectGone = projectPath !== null && !existsSync24(join31(projectPath, "project.godot"));
|
|
49855
50229
|
if (projectGone && callersName !== undefined) {
|
|
49856
50230
|
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"]);
|
|
49857
50231
|
}
|
|
@@ -49861,7 +50235,7 @@ class GodotServer {
|
|
|
49861
50235
|
...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
|
|
49862
50236
|
};
|
|
49863
50237
|
if (run.throughEditor) {
|
|
49864
|
-
return await this.profileOfThePlayedGame(
|
|
50238
|
+
return await this.profileOfThePlayedGame(run, going, fromProject, limit, asked);
|
|
49865
50239
|
}
|
|
49866
50240
|
const path = run.profile;
|
|
49867
50241
|
if (path === undefined) {
|
|
@@ -49874,15 +50248,16 @@ class GodotServer {
|
|
|
49874
50248
|
]);
|
|
49875
50249
|
}
|
|
49876
50250
|
const totals = file.connected ? file : null;
|
|
49877
|
-
return this.
|
|
50251
|
+
return this.profileReply(run, {
|
|
49878
50252
|
through: "gdharness",
|
|
49879
50253
|
running: going,
|
|
49880
50254
|
totals,
|
|
49881
50255
|
coveredMs: totals?.lastFrameAt == null ? 0 : totals.lastFrameAt - totals.startedAt,
|
|
49882
50256
|
...fromProject
|
|
49883
|
-
}, limit)
|
|
50257
|
+
}, limit, asked);
|
|
49884
50258
|
}
|
|
49885
|
-
async profileOfThePlayedGame(
|
|
50259
|
+
async profileOfThePlayedGame(run, going, fromProject, limit, asked) {
|
|
50260
|
+
const { projectPath } = run;
|
|
49886
50261
|
const read = await this.handleViaBridge("profile_read", {});
|
|
49887
50262
|
if (read.isError === true) {
|
|
49888
50263
|
return read;
|
|
@@ -49904,12 +50279,13 @@ class GodotServer {
|
|
|
49904
50279
|
return this.createErrorResponse("The editor says it is playing and has no debugger session for the game, so there is nothing to switch the profiler on in yet.", ["Ask again in a moment, once the game is up"]);
|
|
49905
50280
|
}
|
|
49906
50281
|
const on = await this.firstProfiledFrame();
|
|
50282
|
+
const unmarked = asked.mark || asked.since ? " Nothing was marked, and nothing is counted from a mark: the profile begins now, so an answer without since covers the same span." : "";
|
|
49907
50283
|
return this.jsonTextResponse({
|
|
49908
50284
|
through: "editor",
|
|
49909
50285
|
running: true,
|
|
49910
50286
|
started: true,
|
|
49911
50287
|
functions: [],
|
|
49912
|
-
note: on ?
|
|
50288
|
+
note: on ? `The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}` : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}`
|
|
49913
50289
|
});
|
|
49914
50290
|
}
|
|
49915
50291
|
const functions = asParams(held["functions"]);
|
|
@@ -49932,13 +50308,70 @@ class GodotServer {
|
|
|
49932
50308
|
];
|
|
49933
50309
|
}))
|
|
49934
50310
|
} : null;
|
|
49935
|
-
return this.
|
|
50311
|
+
return this.profileReply(run, {
|
|
49936
50312
|
through: "editor",
|
|
49937
50313
|
running: going,
|
|
49938
50314
|
totals,
|
|
49939
50315
|
coveredMs: readNonNegativeNumber(held, "coveredMs") ?? 0,
|
|
49940
50316
|
...fromProject
|
|
49941
|
-
}, limit)
|
|
50317
|
+
}, limit, asked);
|
|
50318
|
+
}
|
|
50319
|
+
profileReply(run, reading, limit, asked) {
|
|
50320
|
+
const markPath = profileMarkPath(run.startedAt);
|
|
50321
|
+
const now = Date.now();
|
|
50322
|
+
let shown = reading;
|
|
50323
|
+
const lead = [];
|
|
50324
|
+
let sinceFields = {};
|
|
50325
|
+
if (asked.since) {
|
|
50326
|
+
const mark = readProfileMark(markPath);
|
|
50327
|
+
if (mark === null || mark.runStartedAt !== run.startedAt || mark.projectPath !== run.projectPath) {
|
|
50328
|
+
return this.createErrorResponse("No mark has been set in this run, so there is nothing to count from.", ["editor_run profile with mark: true sets one at the totals it answers with"]);
|
|
50329
|
+
}
|
|
50330
|
+
const added = reading.totals === null ? null : totalsSince(reading.totals, mark.totals);
|
|
50331
|
+
if (added === null) {
|
|
50332
|
+
return this.createErrorResponse("The profile holds less than it did at the mark, so the profiler was started again since, and what it holds now cannot be counted from the mark.", ["editor_run profile with mark: true sets a mark in the profile as it is now"]);
|
|
50333
|
+
}
|
|
50334
|
+
shown = {
|
|
50335
|
+
...reading,
|
|
50336
|
+
totals: added.totals,
|
|
50337
|
+
coveredMs: Math.max(0, reading.coveredMs - mark.coveredMs)
|
|
50338
|
+
};
|
|
50339
|
+
const ago = now - mark.takenAt;
|
|
50340
|
+
sinceFields = {
|
|
50341
|
+
since: "mark",
|
|
50342
|
+
markSetMsAgo: ago,
|
|
50343
|
+
...added.shrunk > 0 ? { lessThanAtMark: added.shrunk } : {}
|
|
50344
|
+
};
|
|
50345
|
+
lead.push(`Counted from the mark set ${ago}ms ago: each function's calls and milliseconds are what it added after the mark, frames and coveredMs are those since it, and a function that added no call is left out.`);
|
|
50346
|
+
if (added.shrunk > 0) {
|
|
50347
|
+
const one = added.shrunk === 1;
|
|
50348
|
+
lead.push(`${one ? "One function reads" : `${added.shrunk} functions read`} less than at the mark and ${one ? "is" : "are"} left out, counted under lessThanAtMark: the engine's own totals arrived after the mark and replaced the frames' sum, and capped totals leave out the functions with the least time.`);
|
|
50349
|
+
}
|
|
50350
|
+
}
|
|
50351
|
+
if (asked.mark) {
|
|
50352
|
+
if (reading.totals === null) {
|
|
50353
|
+
return this.createErrorResponse("There is nothing to mark yet: the game has not connected to the profiler.", ["Ask again in a moment, once the game is up"]);
|
|
50354
|
+
}
|
|
50355
|
+
const mark = {
|
|
50356
|
+
takenAt: now,
|
|
50357
|
+
projectPath: run.projectPath,
|
|
50358
|
+
runStartedAt: run.startedAt,
|
|
50359
|
+
coveredMs: reading.coveredMs,
|
|
50360
|
+
totals: reading.totals
|
|
50361
|
+
};
|
|
50362
|
+
if (!writeProfileMark(markPath, mark)) {
|
|
50363
|
+
return this.createErrorResponse(`The mark could not be written to ${markPath}, so none is set.`);
|
|
50364
|
+
}
|
|
50365
|
+
lead.push(asked.since ? 'The mark is moved to the totals as they are now, so the next since: "mark" counts from here.' : 'The mark is set at these totals: editor_run profile with since: "mark" answers only what is added after it.');
|
|
50366
|
+
}
|
|
50367
|
+
const answer = profileAnswer(shown, limit);
|
|
50368
|
+
const note = [...lead, ...typeof answer["note"] === "string" ? [answer["note"]] : []].join(" ");
|
|
50369
|
+
return this.jsonTextResponse({
|
|
50370
|
+
...answer,
|
|
50371
|
+
...sinceFields,
|
|
50372
|
+
...asked.mark ? { marked: true } : {},
|
|
50373
|
+
note: note === "" ? undefined : note
|
|
50374
|
+
});
|
|
49942
50375
|
}
|
|
49943
50376
|
async firstProfiledFrame() {
|
|
49944
50377
|
const deadline = Date.now() + PROFILER_ON_MS;
|
|
@@ -50139,7 +50572,7 @@ class GodotServer {
|
|
|
50139
50572
|
notes.push(endedWithoutACode({
|
|
50140
50573
|
throughEditor: run.throughEditor,
|
|
50141
50574
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
50142
|
-
couldAnnounce: run.projectPath !== null &&
|
|
50575
|
+
couldAnnounce: run.projectPath !== null && existsSync24(join31(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
50143
50576
|
endedHere: typeof run.endedHere === "string",
|
|
50144
50577
|
pickedUp: run.pickedUp === true
|
|
50145
50578
|
}));
|
|
@@ -50769,7 +51202,7 @@ class GodotServer {
|
|
|
50769
51202
|
if (!scriptPath.ok) {
|
|
50770
51203
|
return this.createErrorResponse(scriptPath.reason);
|
|
50771
51204
|
}
|
|
50772
|
-
if (!
|
|
51205
|
+
if (!existsSync24(scriptPath.absolutePath)) {
|
|
50773
51206
|
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
50774
51207
|
}
|
|
50775
51208
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
@@ -50914,6 +51347,45 @@ class GodotServer {
|
|
|
50914
51347
|
...engineExtras(outcome)
|
|
50915
51348
|
});
|
|
50916
51349
|
}
|
|
51350
|
+
async editorOrEngine(command, args, askedAs, effect) {
|
|
51351
|
+
const contained = this.containProjectFiles(args);
|
|
51352
|
+
if (!contained.ok) {
|
|
51353
|
+
return contained.response;
|
|
51354
|
+
}
|
|
51355
|
+
const project = this.project(contained.value);
|
|
51356
|
+
if (!project.ok) {
|
|
51357
|
+
return project.response;
|
|
51358
|
+
}
|
|
51359
|
+
const projectPath = project.value.path;
|
|
51360
|
+
const waited = await this.waitForTheEditorOpenedHere(contained.value, []);
|
|
51361
|
+
if (this.editorHasOpen(projectPath)) {
|
|
51362
|
+
return await this.handleViaBridge(command, contained.value, askedAs);
|
|
51363
|
+
}
|
|
51364
|
+
if (effect === "writes") {
|
|
51365
|
+
if (waited !== null) {
|
|
51366
|
+
return waited;
|
|
51367
|
+
}
|
|
51368
|
+
const arriving = await this.editorsNotYetConnected(projectPath);
|
|
51369
|
+
if (arriving !== undefined && arriving.length > 0) {
|
|
51370
|
+
const named = arriving.map((one) => `pid ${one.pid}`).join(", ");
|
|
51371
|
+
return this.createErrorResponse(`${askedAs} was not run: an editor of this project is running (${named}) and has not reached this server, and saving a scene or resource it holds would put back what a write made around it.`, [
|
|
51372
|
+
"editor_status names it under editorsNotYetConnected and says when it has connected; the write then goes through it",
|
|
51373
|
+
`If it never connects it is not reaching this server: end ${arriving.length === 1 ? "that pid" : "those pids"} and ask again`
|
|
51374
|
+
]);
|
|
51375
|
+
}
|
|
51376
|
+
}
|
|
51377
|
+
const outcome = await this.operation("run_editor_tool", { command, arguments: contained.value }, projectPath);
|
|
51378
|
+
if (!outcome.ok) {
|
|
51379
|
+
return this.answer(outcome);
|
|
51380
|
+
}
|
|
51381
|
+
return this.jsonTextResponse({
|
|
51382
|
+
...outcome.payload,
|
|
51383
|
+
...effect === "reads" ? {
|
|
51384
|
+
readBy: "a headless engine, since no editor serves this project; unsaved changes in an editor are not seen"
|
|
51385
|
+
} : { writtenBy: "a headless engine, since no editor serves this project" },
|
|
51386
|
+
...engineExtras(outcome)
|
|
51387
|
+
});
|
|
51388
|
+
}
|
|
50917
51389
|
async handleMoveFile(given) {
|
|
50918
51390
|
const project = this.project(given);
|
|
50919
51391
|
if (!project.ok) {
|
|
@@ -51312,7 +51784,7 @@ class GodotServer {
|
|
|
51312
51784
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
51313
51785
|
const run = await this.currentRun();
|
|
51314
51786
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
51315
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
51787
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync24(join31(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
51316
51788
|
return null;
|
|
51317
51789
|
}
|
|
51318
51790
|
const already = runtimesAnnounced().running;
|
|
@@ -51321,7 +51793,7 @@ class GodotServer {
|
|
|
51321
51793
|
return !run.announcedBefore.has(one.pid);
|
|
51322
51794
|
}
|
|
51323
51795
|
try {
|
|
51324
|
-
return
|
|
51796
|
+
return statSync14(one.file).mtimeMs >= run.startedAt;
|
|
51325
51797
|
} catch {
|
|
51326
51798
|
return false;
|
|
51327
51799
|
}
|
|
@@ -51402,7 +51874,7 @@ class GodotServer {
|
|
|
51402
51874
|
}
|
|
51403
51875
|
}
|
|
51404
51876
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
51405
|
-
const screenshotPath = screenshotDir ?
|
|
51877
|
+
const screenshotPath = screenshotDir ? join31(screenshotDir, "capture.png") : null;
|
|
51406
51878
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
51407
51879
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
51408
51880
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|
|
@@ -51583,7 +52055,7 @@ async function runGodotServer() {
|
|
|
51583
52055
|
const server = new GodotServer;
|
|
51584
52056
|
await server.run();
|
|
51585
52057
|
}
|
|
51586
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
52058
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
51587
52059
|
var init_server = __esm(() => {
|
|
51588
52060
|
init_dist();
|
|
51589
52061
|
init_stdio();
|
|
@@ -51599,6 +52071,7 @@ var init_server = __esm(() => {
|
|
|
51599
52071
|
init_cut_test_run();
|
|
51600
52072
|
init_dap_client();
|
|
51601
52073
|
init_desktop();
|
|
52074
|
+
init_doc_cache();
|
|
51602
52075
|
init_editor_log();
|
|
51603
52076
|
init_engine_run();
|
|
51604
52077
|
init_errors();
|
|
@@ -51641,7 +52114,7 @@ var init_server = __esm(() => {
|
|
|
51641
52114
|
init_tool_definitions();
|
|
51642
52115
|
init_tool_reference();
|
|
51643
52116
|
init_update_check();
|
|
51644
|
-
__dirname2 =
|
|
52117
|
+
__dirname2 = dirname23(fileURLToPath5(import.meta.url));
|
|
51645
52118
|
KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
|
|
51646
52119
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
51647
52120
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
@@ -51701,8 +52174,8 @@ var init_server = __esm(() => {
|
|
|
51701
52174
|
init_bridge_announce();
|
|
51702
52175
|
init_errors();
|
|
51703
52176
|
init_godot_path();
|
|
51704
|
-
import { existsSync as
|
|
51705
|
-
import { join as
|
|
52177
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
52178
|
+
import { join as join32, resolve as resolve8 } from "node:path";
|
|
51706
52179
|
|
|
51707
52180
|
// src/harnesses.ts
|
|
51708
52181
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -52637,10 +53110,10 @@ init_issues();
|
|
|
52637
53110
|
init_paths();
|
|
52638
53111
|
|
|
52639
53112
|
// src/prompt.ts
|
|
52640
|
-
import
|
|
53113
|
+
import process9 from "node:process";
|
|
52641
53114
|
import { createInterface } from "node:readline/promises";
|
|
52642
53115
|
function interactive() {
|
|
52643
|
-
return
|
|
53116
|
+
return process9.stdin.isTTY && process9.stdout.isTTY;
|
|
52644
53117
|
}
|
|
52645
53118
|
|
|
52646
53119
|
class Ask {
|
|
@@ -52649,7 +53122,7 @@ class Ask {
|
|
|
52649
53122
|
if (!interactive()) {
|
|
52650
53123
|
return fallback;
|
|
52651
53124
|
}
|
|
52652
|
-
this.line ??= createInterface({ input:
|
|
53125
|
+
this.line ??= createInterface({ input: process9.stdin, output: process9.stdout });
|
|
52653
53126
|
const answer = (await this.line.question(`${question} ${fallback ? "[Y/n]" : "[y/N]"} `)).trim();
|
|
52654
53127
|
return answer === "" ? fallback : answer.toLowerCase().startsWith("y");
|
|
52655
53128
|
}
|
|
@@ -52667,10 +53140,10 @@ init_setup();
|
|
|
52667
53140
|
init_setup();
|
|
52668
53141
|
init_tool_definitions();
|
|
52669
53142
|
init_tool_reference();
|
|
52670
|
-
import { existsSync as
|
|
52671
|
-
import { basename as
|
|
53143
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
53144
|
+
import { basename as basename6, dirname as dirname8, join as join14 } from "node:path";
|
|
52672
53145
|
var SKILL_NAME = "gdharness";
|
|
52673
|
-
var SHARED_SKILLS =
|
|
53146
|
+
var SHARED_SKILLS = join14(".agents", "skills");
|
|
52674
53147
|
function skillMarkdown(version) {
|
|
52675
53148
|
return `---
|
|
52676
53149
|
name: ${SKILL_NAME}
|
|
@@ -52757,7 +53230,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
52757
53230
|
| What every label on a screen says | \`runtime_inspect tree\` with \`properties\` naming \`text\`, which reads it off every node that has one; \`includeProperties\` is every stored property of every node and ran to seventy-six thousand characters for one row |
|
|
52758
53231
|
| Whether this engine has a method or property, and what it takes | \`editor_classes info\` with \`member\`, which answers that one member and which class declares it rather than the whole class |
|
|
52759
53232
|
| Wait for a run to finish | \`editor_run wait\`, never a sleep or a shell loop on the pid: it answers as \`editor_output\` does, once the run is over |
|
|
52760
|
-
| Find which GDScript makes a run slow | \`editor_run start\` with \`profile\` set, then \`editor_run profile\`: the engine's own script profiler, per function with calls and self and total milliseconds, by self time, never \`Time.get_ticks_usec()\` laps edited into the game. A scene that does its work in \`_ready\` and quits is covered whole when the runtime autoload is registered, and a game the editor is already playing is profiled from the moment it is asked. \`callersOf\` names where the project's scripts call one function, each place with its function's numbers, since the engine records no callers |
|
|
53233
|
+
| Find which GDScript makes a run slow | \`editor_run start\` with \`profile\` set, then \`editor_run profile\`: the engine's own script profiler, per function with calls and self and total milliseconds, by self time, never \`Time.get_ticks_usec()\` laps edited into the game. A scene that does its work in \`_ready\` and quits is covered whole when the runtime autoload is registered, and a game the editor is already playing is profiled from the moment it is asked. For one action, a press or a commit, set \`mark\` on a profile before it and pass \`since: "mark"\` after it, which answers only what the action added. \`callersOf\` names where the project's scripts call one function, each place with its function's numbers, since the engine records no callers |
|
|
52761
53234
|
| End a bench and the workers it opened | \`editor_run stop\` with \`andChildren\`, never a process listing by scene name: it ends every process under the run's, to any depth, that is a game of the project, announced as one or the project's engine run with \`--path\` on it, names them under \`endedChildren\` with what identified each, and leaves and names any other |
|
|
52762
53235
|
| End a game of the project that no server is holding | \`editor_run stop\` with its \`pid\` from \`editor_status\` \`runtimes\`, never a process kill by hand: it is ended only when it announced itself for the project and its command line is the project's engine run |
|
|
52763
53236
|
| Play a game without touching the player's saves | \`editor_run start\` with \`savesIn\`, a directory for this run's \`user://\`: the editor plays with its own environment, so a run given one is started by the server, and the answer says under \`savesNote\` when the platform ignores it |
|
|
@@ -52799,9 +53272,13 @@ so take it off when the question it was for is answered.
|
|
|
52799
53272
|
|
|
52800
53273
|
\`scene_tree\` for what a scene holds, rather than what the \`.tscn\` text implies, and
|
|
52801
53274
|
\`resource_edit read\` for a resource: both read the file through the editor when one serves the
|
|
52802
|
-
project and through a headless engine when none does, with the same answer. \`
|
|
52803
|
-
\`scene_signal\` and the other \`resource_edit\` ops but \`move\` change it through the
|
|
52804
|
-
|
|
53275
|
+
project and through a headless engine when none does, with the same answer. \`scene_create\`,
|
|
53276
|
+
\`scene_node\`, \`scene_signal\` and the other \`resource_edit\` ops but \`move\` change it through the
|
|
53277
|
+
editor when one serves the project, so the files keep their formatting, and through a headless
|
|
53278
|
+
engine running the editor's own tool when none does. That engine runs the scene's scripts, which an
|
|
53279
|
+
editor runs only as tools, so the answer puts back what they changed as the file loaded, and refuses a
|
|
53280
|
+
file where it cannot, such as a scene whose script does not compile; open the editor
|
|
53281
|
+
(\`editor_launch\` with \`hidden: true\`) and ask again for those. \`script_info\` comes from the editor's own language server, and so does
|
|
52805
53282
|
\`script_diagnostics\` when this project's editor is open; without it a headless engine compiles the
|
|
52806
53283
|
scripts and reports their errors, with lines but no columns and no warnings.
|
|
52807
53284
|
|
|
@@ -52878,7 +53355,9 @@ control outside a headless viewport. Read the refusal instead of retrying the ca
|
|
|
52878
53355
|
Every answer is read back from the engine after the change rather than echoed from the request, and
|
|
52879
53356
|
whatever the engine said on stderr comes back under \`engine_messages\`. A headless engine runs the
|
|
52880
53357
|
project's autoloads after the operation has answered, and what it reports from then until it exits
|
|
52881
|
-
comes back separately, under \`engine_messages_after_answer\`: none of it changes the answer.
|
|
53358
|
+
comes back separately, under \`engine_messages_after_answer\`: none of it changes the answer. A
|
|
53359
|
+
check or write of a script waits for the autoloads to start, so the scripts naming them compile, and
|
|
53360
|
+
what they report while starting comes back under \`engine_messages_before_operation\`.
|
|
52882
53361
|
|
|
52883
53362
|
## More
|
|
52884
53363
|
|
|
@@ -52889,27 +53368,27 @@ comes back separately, under \`engine_messages_after_answer\`: none of it change
|
|
|
52889
53368
|
function skillFiles(version) {
|
|
52890
53369
|
return new Map([
|
|
52891
53370
|
["SKILL.md", skillMarkdown(version)],
|
|
52892
|
-
[
|
|
53371
|
+
[join14("references", "tools.md"), renderToolsMarkdown()]
|
|
52893
53372
|
]);
|
|
52894
53373
|
}
|
|
52895
53374
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
52896
|
-
const directories = [
|
|
53375
|
+
const directories = [join14(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
52897
53376
|
for (const harness of harnesses) {
|
|
52898
53377
|
if (harness.skills === undefined) {
|
|
52899
53378
|
continue;
|
|
52900
53379
|
}
|
|
52901
|
-
const own =
|
|
52902
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
52903
|
-
directories.push(
|
|
53380
|
+
const own = join14(projectPath, harness.skills.dir);
|
|
53381
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join14(own, SKILL_NAME))) {
|
|
53382
|
+
directories.push(join14(own, SKILL_NAME));
|
|
52904
53383
|
}
|
|
52905
53384
|
}
|
|
52906
53385
|
return directories;
|
|
52907
53386
|
}
|
|
52908
53387
|
function everySkillDirectory(projectPath, harnesses) {
|
|
52909
|
-
const directories = new Set([
|
|
53388
|
+
const directories = new Set([join14(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
52910
53389
|
for (const harness of harnesses) {
|
|
52911
53390
|
if (harness.skills !== undefined) {
|
|
52912
|
-
directories.add(
|
|
53391
|
+
directories.add(join14(projectPath, harness.skills.dir, SKILL_NAME));
|
|
52913
53392
|
}
|
|
52914
53393
|
}
|
|
52915
53394
|
return [...directories];
|
|
@@ -52917,33 +53396,33 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
52917
53396
|
function removeSkill(directories, projectPath) {
|
|
52918
53397
|
const removed = [];
|
|
52919
53398
|
for (const directory of directories) {
|
|
52920
|
-
if (
|
|
53399
|
+
if (existsSync14(directory)) {
|
|
52921
53400
|
rmSync6(directory, { recursive: true, force: true });
|
|
52922
|
-
pruneEmpty(
|
|
53401
|
+
pruneEmpty(dirname8(directory), projectPath);
|
|
52923
53402
|
removed.push(directory);
|
|
52924
53403
|
}
|
|
52925
53404
|
}
|
|
52926
53405
|
return removed;
|
|
52927
53406
|
}
|
|
52928
53407
|
function pruneEmpty(directory, projectPath) {
|
|
52929
|
-
const shared =
|
|
53408
|
+
const shared = join14(projectPath, ".agents");
|
|
52930
53409
|
let path = directory;
|
|
52931
|
-
while (path !== projectPath && (
|
|
52932
|
-
if (
|
|
53410
|
+
while (path !== projectPath && (basename6(path) === "skills" || path === shared)) {
|
|
53411
|
+
if (readdirSync9(path).length > 0) {
|
|
52933
53412
|
return;
|
|
52934
53413
|
}
|
|
52935
53414
|
rmSync6(path, { recursive: true, force: true });
|
|
52936
|
-
path =
|
|
53415
|
+
path = dirname8(path);
|
|
52937
53416
|
}
|
|
52938
53417
|
}
|
|
52939
53418
|
function writeSkill(directories, version) {
|
|
52940
53419
|
const files = skillFiles(version);
|
|
52941
53420
|
return directories.map((directory) => {
|
|
52942
|
-
const replaced =
|
|
53421
|
+
const replaced = existsSync14(directory);
|
|
52943
53422
|
rmSync6(directory, { recursive: true, force: true });
|
|
52944
53423
|
for (const [name, contents] of files) {
|
|
52945
|
-
const path =
|
|
52946
|
-
mkdirSync5(
|
|
53424
|
+
const path = join14(directory, name);
|
|
53425
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
52947
53426
|
writeFileSync7(path, contents, "utf8");
|
|
52948
53427
|
}
|
|
52949
53428
|
return { path: directory, replaced };
|
|
@@ -52952,13 +53431,13 @@ function writeSkill(directories, version) {
|
|
|
52952
53431
|
|
|
52953
53432
|
// src/version.ts
|
|
52954
53433
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
52955
|
-
import { dirname as
|
|
53434
|
+
import { dirname as dirname9, join as join15 } from "node:path";
|
|
52956
53435
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
52957
53436
|
function getLocalVersion() {
|
|
52958
|
-
const currentDirectory =
|
|
53437
|
+
const currentDirectory = dirname9(fileURLToPath2(import.meta.url));
|
|
52959
53438
|
const packageCandidates = [
|
|
52960
|
-
|
|
52961
|
-
|
|
53439
|
+
join15(currentDirectory, "..", "package.json"),
|
|
53440
|
+
join15(currentDirectory, "..", "..", "package.json")
|
|
52962
53441
|
];
|
|
52963
53442
|
for (const packagePath of packageCandidates) {
|
|
52964
53443
|
try {
|
|
@@ -52980,7 +53459,7 @@ class UsageError extends Refusal {
|
|
|
52980
53459
|
function projectArgument(at) {
|
|
52981
53460
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
52982
53461
|
const projectPath = resolve8(named ?? ".");
|
|
52983
|
-
if (!
|
|
53462
|
+
if (!existsSync25(join32(projectPath, "project.godot"))) {
|
|
52984
53463
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
52985
53464
|
}
|
|
52986
53465
|
return projectPath;
|
|
@@ -53025,7 +53504,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
53025
53504
|
return;
|
|
53026
53505
|
}
|
|
53027
53506
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
53028
|
-
if (!
|
|
53507
|
+
if (!existsSync25(join32(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
53029
53508
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
53030
53509
|
}
|
|
53031
53510
|
}
|
|
@@ -53157,7 +53636,7 @@ async function setup() {
|
|
|
53157
53636
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
53158
53637
|
}
|
|
53159
53638
|
if (!args.includes("--no-skill")) {
|
|
53160
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
53639
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync25);
|
|
53161
53640
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
53162
53641
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
53163
53642
|
}
|
|
@@ -53271,7 +53750,7 @@ async function upgrade() {
|
|
|
53271
53750
|
moved.push(took.from);
|
|
53272
53751
|
}
|
|
53273
53752
|
}
|
|
53274
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
53753
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync25), version)) {
|
|
53275
53754
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
53276
53755
|
}
|
|
53277
53756
|
console.log(`
|