gdharness 1.3.7 → 1.3.9
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 +999 -864
- package/build/godot/addons/gdharness_editor/file_reads.gd +13 -0
- package/build/godot/addons/gdharness_editor/reading.gd +61 -5
- package/build/godot/addons/gdharness_editor/scene_file.gd +41 -35
- package/build/godot/addons/gdharness_editor/serialisation.gd +8 -7
- package/build/godot/addons/gdharness_editor/tool_executor.gd +12 -2
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +244 -0
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +105 -157
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +25 -1
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -1
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +72 -3
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +7 -1
- package/build/godot/addons/gdharness_runtime/call_errors.gd +8 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +61 -5
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +6 -1
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +74 -54
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +38 -6
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +2 -0
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +26 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +105 -6
- package/build/godot/addons/gdharness_runtime/serialisation.gd +8 -7
- package/build/godot/operations/dependencies.gd +61 -10
- package/build/godot/operations/editor_tools.gd +8 -0
- package/build/godot/operations/file_reads.gd +13 -0
- package/build/godot/operations/gdscript_analysis.gd +76 -6
- package/build/godot/operations/gdscript_authoring.gd +5 -2
- package/build/godot/operations/godot_operations.gd +4 -0
- package/build/godot/operations/import_pipeline.gd +45 -1
- package/build/godot/operations/input_actions.gd +22 -7
- package/build/godot/operations/project_config.gd +31 -0
- package/build/godot/operations/project_diagnostics.gd +6 -5
- package/build/godot/operations/reading.gd +61 -5
- package/build/godot/operations/serialisation.gd +8 -7
- package/build/keeper.js +44 -13
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -8367,9 +8367,493 @@ var init_doc_cache = __esm(() => {
|
|
|
8367
8367
|
CACHE_FILE = /^editor_doc_cache-\d+\.\d+\.res$/;
|
|
8368
8368
|
});
|
|
8369
8369
|
|
|
8370
|
+
// src/project-scan.ts
|
|
8371
|
+
import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
8372
|
+
import { extname, join as join7 } from "node:path";
|
|
8373
|
+
function skipped(name) {
|
|
8374
|
+
return name.startsWith(".") || SKIPPED.has(name);
|
|
8375
|
+
}
|
|
8376
|
+
function steppedOver(directory) {
|
|
8377
|
+
return gdignored(directory) || installedByUs(directory);
|
|
8378
|
+
}
|
|
8379
|
+
function gdignored(directory) {
|
|
8380
|
+
return existsSync6(join7(directory, ".gdignore"));
|
|
8381
|
+
}
|
|
8382
|
+
function installedByUs(directory) {
|
|
8383
|
+
return existsSync6(join7(directory, VERSION_MARKER));
|
|
8384
|
+
}
|
|
8385
|
+
function projectStructure(projectPath) {
|
|
8386
|
+
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
8387
|
+
const visit = (directory) => {
|
|
8388
|
+
if (steppedOver(directory)) {
|
|
8389
|
+
return;
|
|
8390
|
+
}
|
|
8391
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8392
|
+
if (skipped(entry.name)) {
|
|
8393
|
+
continue;
|
|
8394
|
+
}
|
|
8395
|
+
if (entry.isDirectory()) {
|
|
8396
|
+
visit(join7(directory, entry.name));
|
|
8397
|
+
} else if (entry.isFile()) {
|
|
8398
|
+
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
8399
|
+
if (SIDECAR_EXTENSIONS.has(extension)) {
|
|
8400
|
+
continue;
|
|
8401
|
+
}
|
|
8402
|
+
if (SCENE_EXTENSIONS.has(extension)) {
|
|
8403
|
+
structure.scenes += 1;
|
|
8404
|
+
} else if (extension === "gd" || extension === "cs") {
|
|
8405
|
+
structure.scripts += 1;
|
|
8406
|
+
} else if (ASSET_EXTENSIONS.has(extension)) {
|
|
8407
|
+
structure.assets += 1;
|
|
8408
|
+
} else {
|
|
8409
|
+
structure.other += 1;
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
}
|
|
8413
|
+
};
|
|
8414
|
+
visit(projectPath);
|
|
8415
|
+
return structure;
|
|
8416
|
+
}
|
|
8417
|
+
function projectFiles(projectPath, extensions) {
|
|
8418
|
+
return filesBelow(projectPath, extensions);
|
|
8419
|
+
}
|
|
8420
|
+
function folderFiles(directory, extensions) {
|
|
8421
|
+
return gdignored(directory) ? [] : filesBelow(directory, extensions);
|
|
8422
|
+
}
|
|
8423
|
+
function filesBelow(root, extensions) {
|
|
8424
|
+
const found = [];
|
|
8425
|
+
const visit = (directory, prefix) => {
|
|
8426
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8427
|
+
if (skipped(entry.name)) {
|
|
8428
|
+
continue;
|
|
8429
|
+
}
|
|
8430
|
+
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
8431
|
+
const path = join7(directory, entry.name);
|
|
8432
|
+
if (entry.isDirectory()) {
|
|
8433
|
+
if (!steppedOver(path)) {
|
|
8434
|
+
visit(path, spelled);
|
|
8435
|
+
}
|
|
8436
|
+
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
8437
|
+
found.push(spelled);
|
|
8438
|
+
}
|
|
8439
|
+
}
|
|
8440
|
+
};
|
|
8441
|
+
visit(root, "");
|
|
8442
|
+
return found.sort();
|
|
8443
|
+
}
|
|
8444
|
+
function scriptsWithoutUid(projectPath) {
|
|
8445
|
+
const found = [];
|
|
8446
|
+
const visit = (directory, prefix) => {
|
|
8447
|
+
if (steppedOver(directory)) {
|
|
8448
|
+
return;
|
|
8449
|
+
}
|
|
8450
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8451
|
+
if (skipped(entry.name)) {
|
|
8452
|
+
continue;
|
|
8453
|
+
}
|
|
8454
|
+
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
8455
|
+
if (entry.isDirectory()) {
|
|
8456
|
+
visit(join7(directory, entry.name), spelled);
|
|
8457
|
+
continue;
|
|
8458
|
+
}
|
|
8459
|
+
if (!entry.isFile()) {
|
|
8460
|
+
continue;
|
|
8461
|
+
}
|
|
8462
|
+
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
8463
|
+
if (CARRIES_A_UID.has(extension) && !existsSync6(join7(directory, `${entry.name}.uid`))) {
|
|
8464
|
+
found.push(spelled);
|
|
8465
|
+
}
|
|
8466
|
+
}
|
|
8467
|
+
};
|
|
8468
|
+
visit(projectPath, "");
|
|
8469
|
+
return found.sort();
|
|
8470
|
+
}
|
|
8471
|
+
function pathsInUidCache(projectPath) {
|
|
8472
|
+
let data;
|
|
8473
|
+
try {
|
|
8474
|
+
data = readFileSync3(join7(projectPath, ".godot", "uid_cache.bin"));
|
|
8475
|
+
} catch {
|
|
8476
|
+
return null;
|
|
8477
|
+
}
|
|
8478
|
+
if (data.length < 4) {
|
|
8479
|
+
return null;
|
|
8480
|
+
}
|
|
8481
|
+
const count = data.readUInt32LE(0);
|
|
8482
|
+
const paths = new Map;
|
|
8483
|
+
let at = 4;
|
|
8484
|
+
for (let entry = 0;entry < count; entry++) {
|
|
8485
|
+
if (at + 12 > data.length) {
|
|
8486
|
+
return null;
|
|
8487
|
+
}
|
|
8488
|
+
const id = data.readBigUInt64LE(at);
|
|
8489
|
+
const length = data.readUInt32LE(at + 8);
|
|
8490
|
+
at += 12;
|
|
8491
|
+
if (at + length > data.length) {
|
|
8492
|
+
return null;
|
|
8493
|
+
}
|
|
8494
|
+
paths.set(id, data.toString("utf8", at, at + length));
|
|
8495
|
+
at += length;
|
|
8496
|
+
}
|
|
8497
|
+
return new Set(paths.values());
|
|
8498
|
+
}
|
|
8499
|
+
function isBinary(bytes) {
|
|
8500
|
+
return bytes.subarray(0, 8000).includes(0);
|
|
8501
|
+
}
|
|
8502
|
+
function projectTexts(projectPath) {
|
|
8503
|
+
const found = [];
|
|
8504
|
+
const visit = (directory, prefix) => {
|
|
8505
|
+
if (steppedOver(directory)) {
|
|
8506
|
+
return;
|
|
8507
|
+
}
|
|
8508
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8509
|
+
if (skipped(entry.name)) {
|
|
8510
|
+
continue;
|
|
8511
|
+
}
|
|
8512
|
+
const absolute = join7(directory, entry.name);
|
|
8513
|
+
const spelled = `${prefix}${entry.name}`;
|
|
8514
|
+
if (entry.isDirectory()) {
|
|
8515
|
+
visit(absolute, `${spelled}/`);
|
|
8516
|
+
continue;
|
|
8517
|
+
}
|
|
8518
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
8519
|
+
if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
|
|
8520
|
+
continue;
|
|
8521
|
+
}
|
|
8522
|
+
const bytes = readFileSync3(absolute);
|
|
8523
|
+
if (!isBinary(bytes)) {
|
|
8524
|
+
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
8527
|
+
};
|
|
8528
|
+
visit(projectPath, "");
|
|
8529
|
+
return found;
|
|
8530
|
+
}
|
|
8531
|
+
function ignoredTexts(projectPath) {
|
|
8532
|
+
const found = [];
|
|
8533
|
+
const visit = (directory, prefix, ignored) => {
|
|
8534
|
+
if (installedByUs(directory)) {
|
|
8535
|
+
return;
|
|
8536
|
+
}
|
|
8537
|
+
const under = ignored || gdignored(directory);
|
|
8538
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8539
|
+
if (skipped(entry.name)) {
|
|
8540
|
+
continue;
|
|
8541
|
+
}
|
|
8542
|
+
const absolute = join7(directory, entry.name);
|
|
8543
|
+
const spelled = `${prefix}${entry.name}`;
|
|
8544
|
+
if (entry.isDirectory()) {
|
|
8545
|
+
visit(absolute, `${spelled}/`, under);
|
|
8546
|
+
continue;
|
|
8547
|
+
}
|
|
8548
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
8549
|
+
if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
|
|
8550
|
+
continue;
|
|
8551
|
+
}
|
|
8552
|
+
const bytes = readFileSync3(absolute);
|
|
8553
|
+
if (!isBinary(bytes)) {
|
|
8554
|
+
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
8555
|
+
}
|
|
8556
|
+
}
|
|
8557
|
+
};
|
|
8558
|
+
visit(projectPath, "", false);
|
|
8559
|
+
return found;
|
|
8560
|
+
}
|
|
8561
|
+
function plainMatch(line, needle, caseSensitive) {
|
|
8562
|
+
if (caseSensitive) {
|
|
8563
|
+
return line.includes(needle) ? [needle] : null;
|
|
8564
|
+
}
|
|
8565
|
+
const lowered = line.toLowerCase();
|
|
8566
|
+
const at = lowered.indexOf(needle);
|
|
8567
|
+
if (at < 0) {
|
|
8568
|
+
return null;
|
|
8569
|
+
}
|
|
8570
|
+
return [lowered.length === line.length ? line.slice(at, at + needle.length) : needle];
|
|
8571
|
+
}
|
|
8572
|
+
function compiledQuery(query, caseSensitive) {
|
|
8573
|
+
try {
|
|
8574
|
+
return new RegExp(query, caseSensitive ? "" : "i");
|
|
8575
|
+
} catch (error) {
|
|
8576
|
+
const why = errorMessage(error).split(": ").at(-1) ?? "";
|
|
8577
|
+
throw new Error(`'${query}' is not a regular expression: ${why.charAt(0).toLowerCase()}${why.slice(1)}. With regex true, a character such as ( ) [ ] { } . * + ? | ^ $ or \\ stands for itself only after a backslash, as in \\(; or leave regex out to search for the text as written.`);
|
|
8578
|
+
}
|
|
8579
|
+
}
|
|
8580
|
+
function searchProject(projectPath, options) {
|
|
8581
|
+
const extensions = options.fileTypes === undefined ? null : new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
|
|
8582
|
+
const result = {
|
|
8583
|
+
query: options.query,
|
|
8584
|
+
results: [],
|
|
8585
|
+
summary: { files_searched: 0, files_with_matches: 0, total_matches: 0, truncated: false }
|
|
8586
|
+
};
|
|
8587
|
+
const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
|
|
8588
|
+
const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
|
|
8589
|
+
const full = () => result.summary.truncated;
|
|
8590
|
+
const notSearched = [];
|
|
8591
|
+
const visit = (directory) => {
|
|
8592
|
+
if (steppedOver(directory)) {
|
|
8593
|
+
if (gdignored(directory)) {
|
|
8594
|
+
notSearched.push(`res://${directory.slice(projectPath.length + 1).replace(/\\/g, "/")}/`);
|
|
8595
|
+
}
|
|
8596
|
+
return;
|
|
8597
|
+
}
|
|
8598
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8599
|
+
if (full()) {
|
|
8600
|
+
return;
|
|
8601
|
+
}
|
|
8602
|
+
if (skipped(entry.name)) {
|
|
8603
|
+
continue;
|
|
8604
|
+
}
|
|
8605
|
+
const entryPath = join7(directory, entry.name);
|
|
8606
|
+
if (entry.isDirectory()) {
|
|
8607
|
+
visit(entryPath);
|
|
8608
|
+
continue;
|
|
8609
|
+
}
|
|
8610
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
8611
|
+
if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
|
|
8612
|
+
continue;
|
|
8613
|
+
}
|
|
8614
|
+
const bytes = readFileSync3(entryPath);
|
|
8615
|
+
if (extensions === null && isBinary(bytes)) {
|
|
8616
|
+
continue;
|
|
8617
|
+
}
|
|
8618
|
+
result.summary.files_searched += 1;
|
|
8619
|
+
const matches = [];
|
|
8620
|
+
for (const [index, line] of bytes.toString("utf8").split(/\r?\n/).entries()) {
|
|
8621
|
+
const found = regex ? regex.exec(line) : plainMatch(line, needle, options.caseSensitive);
|
|
8622
|
+
if (found === null) {
|
|
8623
|
+
continue;
|
|
8624
|
+
}
|
|
8625
|
+
if (result.summary.total_matches >= options.maxResults) {
|
|
8626
|
+
result.summary.truncated = true;
|
|
8627
|
+
break;
|
|
8628
|
+
}
|
|
8629
|
+
matches.push({ line: index + 1, content: line.trim(), match: found[0] });
|
|
8630
|
+
result.summary.total_matches += 1;
|
|
8631
|
+
}
|
|
8632
|
+
if (matches.length > 0) {
|
|
8633
|
+
const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
|
|
8634
|
+
result.results.push({ file: `res://${relativePath}`, matches });
|
|
8635
|
+
result.summary.files_with_matches += 1;
|
|
8636
|
+
}
|
|
8637
|
+
}
|
|
8638
|
+
};
|
|
8639
|
+
visit(projectPath);
|
|
8640
|
+
return notSearched.length === 0 ? result : { ...result, not_searched: notSearched };
|
|
8641
|
+
}
|
|
8642
|
+
var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, SIDECAR_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
|
|
8643
|
+
var init_project_scan = __esm(() => {
|
|
8644
|
+
init_errors();
|
|
8645
|
+
SKIPPED = new Set(["node_modules"]);
|
|
8646
|
+
SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
8647
|
+
SIDECAR_EXTENSIONS = new Set(["import", "uid"]);
|
|
8648
|
+
ASSET_EXTENSIONS = new Set([
|
|
8649
|
+
"png",
|
|
8650
|
+
"jpg",
|
|
8651
|
+
"jpeg",
|
|
8652
|
+
"webp",
|
|
8653
|
+
"svg",
|
|
8654
|
+
"bmp",
|
|
8655
|
+
"tga",
|
|
8656
|
+
"exr",
|
|
8657
|
+
"hdr",
|
|
8658
|
+
"ktx",
|
|
8659
|
+
"dds",
|
|
8660
|
+
"ttf",
|
|
8661
|
+
"otf",
|
|
8662
|
+
"woff",
|
|
8663
|
+
"woff2",
|
|
8664
|
+
"fnt",
|
|
8665
|
+
"wav",
|
|
8666
|
+
"mp3",
|
|
8667
|
+
"ogg",
|
|
8668
|
+
"glb",
|
|
8669
|
+
"gltf",
|
|
8670
|
+
"obj",
|
|
8671
|
+
"fbx",
|
|
8672
|
+
"blend",
|
|
8673
|
+
"dae",
|
|
8674
|
+
"ogv",
|
|
8675
|
+
"gdshader",
|
|
8676
|
+
"shader"
|
|
8677
|
+
]);
|
|
8678
|
+
CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
8679
|
+
BINARY_EXTENSIONS = new Set([
|
|
8680
|
+
...[...ASSET_EXTENSIONS].filter((extension) => !["svg", "gltf", "gdshader", "shader", "fnt"].includes(extension)),
|
|
8681
|
+
"scn",
|
|
8682
|
+
"res",
|
|
8683
|
+
"zip",
|
|
8684
|
+
"pck",
|
|
8685
|
+
"exe",
|
|
8686
|
+
"dll",
|
|
8687
|
+
"so",
|
|
8688
|
+
"dylib"
|
|
8689
|
+
]);
|
|
8690
|
+
});
|
|
8691
|
+
|
|
8692
|
+
// src/import-freshness.ts
|
|
8693
|
+
import { createHash } from "node:crypto";
|
|
8694
|
+
import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync4, statSync as statSync5 } from "node:fs";
|
|
8695
|
+
import { join as join8 } from "node:path";
|
|
8696
|
+
function md5Of(file) {
|
|
8697
|
+
let stats;
|
|
8698
|
+
try {
|
|
8699
|
+
stats = statSync5(file);
|
|
8700
|
+
} catch {
|
|
8701
|
+
return null;
|
|
8702
|
+
}
|
|
8703
|
+
const known = hashes.get(file);
|
|
8704
|
+
if (known !== undefined && known.size === stats.size && known.mtimeMs === stats.mtimeMs) {
|
|
8705
|
+
return known.md5;
|
|
8706
|
+
}
|
|
8707
|
+
let md5;
|
|
8708
|
+
try {
|
|
8709
|
+
md5 = createHash("md5").update(readFileSync4(file)).digest("hex");
|
|
8710
|
+
} catch {
|
|
8711
|
+
return null;
|
|
8712
|
+
}
|
|
8713
|
+
hashes.set(file, { size: stats.size, mtimeMs: stats.mtimeMs, md5 });
|
|
8714
|
+
return md5;
|
|
8715
|
+
}
|
|
8716
|
+
function sectionOf(text, section) {
|
|
8717
|
+
const keys = new Map;
|
|
8718
|
+
let inside = false;
|
|
8719
|
+
for (const line of text.split(/\r?\n/)) {
|
|
8720
|
+
const header = /^\[([^\]]+)\]\s*$/.exec(line);
|
|
8721
|
+
if (header !== null) {
|
|
8722
|
+
inside = header[1] === section;
|
|
8723
|
+
continue;
|
|
8724
|
+
}
|
|
8725
|
+
const pair = inside ? /^([A-Za-z0-9_./]+)=(.*)$/.exec(line) : null;
|
|
8726
|
+
if (pair?.[1] !== undefined && pair[2] !== undefined) {
|
|
8727
|
+
keys.set(pair[1], pair[2]);
|
|
8728
|
+
}
|
|
8729
|
+
}
|
|
8730
|
+
return keys;
|
|
8731
|
+
}
|
|
8732
|
+
function unquoted(value) {
|
|
8733
|
+
return value === undefined ? "" : value.replace(/^"(.*)"$/, "$1");
|
|
8734
|
+
}
|
|
8735
|
+
function listed(value) {
|
|
8736
|
+
return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
|
|
8737
|
+
}
|
|
8738
|
+
function onDisk(projectPath, resPath) {
|
|
8739
|
+
return join8(projectPath, resPath.slice("res://".length));
|
|
8740
|
+
}
|
|
8741
|
+
function behind(projectPath, resPath, sidecar) {
|
|
8742
|
+
const remap = sectionOf(sidecar, "remap");
|
|
8743
|
+
const importer = unquoted(remap.get("importer"));
|
|
8744
|
+
if (importer === "keep" || importer === "skip") {
|
|
8745
|
+
return null;
|
|
8746
|
+
}
|
|
8747
|
+
const outputs = listed(sectionOf(sidecar, "deps").get("dest_files"));
|
|
8748
|
+
if (outputs.some((output) => !existsSync7(onDisk(projectPath, output)))) {
|
|
8749
|
+
return "its imported output is not on disk";
|
|
8750
|
+
}
|
|
8751
|
+
const name = resPath.slice(resPath.lastIndexOf("/") + 1);
|
|
8752
|
+
const record = join8(projectPath, ".godot", "imported", `${name}-${createHash("md5").update(resPath, "utf8").digest("hex")}.md5`);
|
|
8753
|
+
let recorded = "";
|
|
8754
|
+
try {
|
|
8755
|
+
recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync4(record, "utf8"))?.[1] ?? "";
|
|
8756
|
+
} catch {
|
|
8757
|
+
recorded = "";
|
|
8758
|
+
}
|
|
8759
|
+
if (recorded === "") {
|
|
8760
|
+
return "its import recorded no hash of its source";
|
|
8761
|
+
}
|
|
8762
|
+
const current = md5Of(onDisk(projectPath, resPath));
|
|
8763
|
+
return current === null || current === recorded ? null : "the source changed after it was imported";
|
|
8764
|
+
}
|
|
8765
|
+
function staleImports(projectPath) {
|
|
8766
|
+
const stale = [];
|
|
8767
|
+
const visit = (directory, resDirectory) => {
|
|
8768
|
+
if (existsSync7(join8(directory, ".gdignore")) || existsSync7(join8(directory, VERSION_MARKER))) {
|
|
8769
|
+
return;
|
|
8770
|
+
}
|
|
8771
|
+
let entries;
|
|
8772
|
+
try {
|
|
8773
|
+
entries = readdirSync5(directory, { withFileTypes: true });
|
|
8774
|
+
} catch {
|
|
8775
|
+
return;
|
|
8776
|
+
}
|
|
8777
|
+
const names = new Set(entries.map((entry) => entry.name));
|
|
8778
|
+
for (const entry of entries) {
|
|
8779
|
+
if (entry.name.startsWith(".")) {
|
|
8780
|
+
continue;
|
|
8781
|
+
}
|
|
8782
|
+
const resPath = `${resDirectory}${entry.name}`;
|
|
8783
|
+
if (entry.isDirectory()) {
|
|
8784
|
+
visit(join8(directory, entry.name), `${resPath}/`);
|
|
8785
|
+
continue;
|
|
8786
|
+
}
|
|
8787
|
+
if (!entry.isFile()) {
|
|
8788
|
+
continue;
|
|
8789
|
+
}
|
|
8790
|
+
if (entry.name.endsWith(".import")) {
|
|
8791
|
+
const source = resPath.slice(0, -".import".length);
|
|
8792
|
+
if (!names.has(entry.name.slice(0, -".import".length))) {
|
|
8793
|
+
continue;
|
|
8794
|
+
}
|
|
8795
|
+
let sidecar;
|
|
8796
|
+
try {
|
|
8797
|
+
sidecar = readFileSync4(join8(directory, entry.name), "utf8");
|
|
8798
|
+
} catch {
|
|
8799
|
+
continue;
|
|
8800
|
+
}
|
|
8801
|
+
const reason = behind(projectPath, source, sidecar);
|
|
8802
|
+
if (reason !== null) {
|
|
8803
|
+
stale.push({ path: source, reason });
|
|
8804
|
+
}
|
|
8805
|
+
continue;
|
|
8806
|
+
}
|
|
8807
|
+
const extension = entry.name.slice(entry.name.lastIndexOf(".") + 1).toLowerCase();
|
|
8808
|
+
if (IMPORTABLE.has(extension) && !names.has(`${entry.name}.import`)) {
|
|
8809
|
+
stale.push({ path: resPath, reason: "it has not been imported" });
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
};
|
|
8813
|
+
visit(projectPath, "res://");
|
|
8814
|
+
return stale.sort((one, other) => one.path.localeCompare(other.path));
|
|
8815
|
+
}
|
|
8816
|
+
var IMPORTABLE_EXTENSIONS, IMPORTABLE, hashes;
|
|
8817
|
+
var init_import_freshness = __esm(() => {
|
|
8818
|
+
init_project_scan();
|
|
8819
|
+
IMPORTABLE_EXTENSIONS = [
|
|
8820
|
+
"png",
|
|
8821
|
+
"jpg",
|
|
8822
|
+
"jpeg",
|
|
8823
|
+
"webp",
|
|
8824
|
+
"svg",
|
|
8825
|
+
"bmp",
|
|
8826
|
+
"tga",
|
|
8827
|
+
"exr",
|
|
8828
|
+
"hdr",
|
|
8829
|
+
"dds",
|
|
8830
|
+
"ktx",
|
|
8831
|
+
"wav",
|
|
8832
|
+
"mp3",
|
|
8833
|
+
"ogg",
|
|
8834
|
+
"ttf",
|
|
8835
|
+
"otf",
|
|
8836
|
+
"ttc",
|
|
8837
|
+
"otc",
|
|
8838
|
+
"woff",
|
|
8839
|
+
"woff2",
|
|
8840
|
+
"pfb",
|
|
8841
|
+
"pfm",
|
|
8842
|
+
"fnt",
|
|
8843
|
+
"font",
|
|
8844
|
+
"glb",
|
|
8845
|
+
"gltf",
|
|
8846
|
+
"fbx",
|
|
8847
|
+
"obj",
|
|
8848
|
+
"dae"
|
|
8849
|
+
];
|
|
8850
|
+
IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
|
|
8851
|
+
hashes = new Map;
|
|
8852
|
+
});
|
|
8853
|
+
|
|
8370
8854
|
// src/library-copies.ts
|
|
8371
|
-
import { existsSync as
|
|
8372
|
-
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute3, join as
|
|
8855
|
+
import { existsSync as existsSync8, readdirSync as readdirSync6, readFileSync as readFileSync5, renameSync, unlinkSync } from "node:fs";
|
|
8856
|
+
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute3, join as join9 } from "node:path";
|
|
8373
8857
|
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
8374
8858
|
const setAside = [];
|
|
8375
8859
|
const swept = [];
|
|
@@ -8379,24 +8863,24 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
8379
8863
|
for (const library of windowsLibraries(projectPath)) {
|
|
8380
8864
|
const folder = dirname6(library);
|
|
8381
8865
|
const copy = `~${basename5(library)}`;
|
|
8382
|
-
if (
|
|
8383
|
-
const aside =
|
|
8866
|
+
if (existsSync8(join9(folder, copy))) {
|
|
8867
|
+
const aside = join9(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
8384
8868
|
try {
|
|
8385
|
-
renameSync(
|
|
8869
|
+
renameSync(join9(folder, copy), aside);
|
|
8386
8870
|
setAside.push(aside);
|
|
8387
8871
|
} catch {}
|
|
8388
8872
|
}
|
|
8389
8873
|
let names;
|
|
8390
8874
|
try {
|
|
8391
|
-
names =
|
|
8875
|
+
names = readdirSync6(folder);
|
|
8392
8876
|
} catch {
|
|
8393
8877
|
continue;
|
|
8394
8878
|
}
|
|
8395
8879
|
for (const name of names) {
|
|
8396
8880
|
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
8397
8881
|
try {
|
|
8398
|
-
unlinkSync(
|
|
8399
|
-
swept.push(
|
|
8882
|
+
unlinkSync(join9(folder, name));
|
|
8883
|
+
swept.push(join9(folder, name));
|
|
8400
8884
|
} catch {}
|
|
8401
8885
|
}
|
|
8402
8886
|
}
|
|
@@ -8404,7 +8888,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
8404
8888
|
return { setAside, swept };
|
|
8405
8889
|
}
|
|
8406
8890
|
function windowsLibraries(projectPath) {
|
|
8407
|
-
const listed = readOrNull(
|
|
8891
|
+
const listed = readOrNull(join9(projectPath, ".godot", "extension_list.cfg"));
|
|
8408
8892
|
if (listed === null) {
|
|
8409
8893
|
return [];
|
|
8410
8894
|
}
|
|
@@ -8436,13 +8920,13 @@ function resolveResource(projectPath, relativeTo, path) {
|
|
|
8436
8920
|
return null;
|
|
8437
8921
|
}
|
|
8438
8922
|
if (path.startsWith("res://")) {
|
|
8439
|
-
return
|
|
8923
|
+
return join9(projectPath, path.slice("res://".length));
|
|
8440
8924
|
}
|
|
8441
|
-
return isAbsolute3(path) ? path :
|
|
8925
|
+
return isAbsolute3(path) ? path : join9(relativeTo, path);
|
|
8442
8926
|
}
|
|
8443
8927
|
function readOrNull(path) {
|
|
8444
8928
|
try {
|
|
8445
|
-
return
|
|
8929
|
+
return readFileSync5(path, "utf8");
|
|
8446
8930
|
} catch {
|
|
8447
8931
|
return null;
|
|
8448
8932
|
}
|
|
@@ -8450,23 +8934,23 @@ function readOrNull(path) {
|
|
|
8450
8934
|
var init_library_copies = () => {};
|
|
8451
8935
|
|
|
8452
8936
|
// src/reimport.ts
|
|
8453
|
-
import { createHash } from "node:crypto";
|
|
8454
|
-
import { existsSync as
|
|
8455
|
-
import { basename as basename6, join as
|
|
8937
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
8938
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync6, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8939
|
+
import { basename as basename6, join as join10 } from "node:path";
|
|
8456
8940
|
function forceNextImport(projectPath, resourcePath) {
|
|
8457
8941
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
8458
|
-
const sidecar =
|
|
8459
|
-
if (!
|
|
8942
|
+
const sidecar = join10(projectPath, `${relative}.import`);
|
|
8943
|
+
if (!existsSync9(sidecar)) {
|
|
8460
8944
|
return;
|
|
8461
8945
|
}
|
|
8462
|
-
const { atime, mtimeMs } =
|
|
8463
|
-
writeFileSync3(sidecar,
|
|
8946
|
+
const { atime, mtimeMs } = statSync6(sidecar);
|
|
8947
|
+
writeFileSync3(sidecar, readFileSync6(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
8464
8948
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
8465
8949
|
rmSync3(importRecord(projectPath, resourcePath), { force: true });
|
|
8466
8950
|
}
|
|
8467
8951
|
function importRecord(projectPath, resourcePath) {
|
|
8468
|
-
const digest =
|
|
8469
|
-
return
|
|
8952
|
+
const digest = createHash2("md5").update(resourcePath).digest("hex");
|
|
8953
|
+
return join10(projectPath, ".godot", "imported", `${basename6(resourcePath)}-${digest}.md5`);
|
|
8470
8954
|
}
|
|
8471
8955
|
var init_reimport = () => {};
|
|
8472
8956
|
|
|
@@ -8496,17 +8980,17 @@ function alive(pid) {
|
|
|
8496
8980
|
var init_alive = () => {};
|
|
8497
8981
|
|
|
8498
8982
|
// src/scratch.ts
|
|
8499
|
-
import { existsSync as
|
|
8983
|
+
import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync7, rmSync as rmSync4 } from "node:fs";
|
|
8500
8984
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8501
|
-
import { join as
|
|
8985
|
+
import { join as join11 } from "node:path";
|
|
8502
8986
|
import process8 from "node:process";
|
|
8503
8987
|
function scratchDirectory(kind) {
|
|
8504
|
-
return mkdtempSync(
|
|
8988
|
+
return mkdtempSync(join11(tmpdir2(), `gdharness-${kind}-${process8.pid}-`));
|
|
8505
8989
|
}
|
|
8506
8990
|
function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
8507
8991
|
let entries;
|
|
8508
8992
|
try {
|
|
8509
|
-
entries =
|
|
8993
|
+
entries = readdirSync7(directory);
|
|
8510
8994
|
} catch {
|
|
8511
8995
|
return;
|
|
8512
8996
|
}
|
|
@@ -8517,7 +9001,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
|
8517
9001
|
continue;
|
|
8518
9002
|
}
|
|
8519
9003
|
try {
|
|
8520
|
-
remove(
|
|
9004
|
+
remove(join11(directory, entry));
|
|
8521
9005
|
} catch {}
|
|
8522
9006
|
}
|
|
8523
9007
|
}
|
|
@@ -8555,7 +9039,7 @@ function tryAgain(path, attempt) {
|
|
|
8555
9039
|
lastTryOnExit();
|
|
8556
9040
|
}
|
|
8557
9041
|
const after = AGAIN_AFTER_MS[attempt];
|
|
8558
|
-
if (after === undefined || !
|
|
9042
|
+
if (after === undefined || !existsSync10(path)) {
|
|
8559
9043
|
waiting.delete(path);
|
|
8560
9044
|
if (after === undefined) {
|
|
8561
9045
|
atExit.add(path);
|
|
@@ -8744,6 +9228,29 @@ function ignoredYetRan(report, ignored) {
|
|
|
8744
9228
|
}
|
|
8745
9229
|
return found;
|
|
8746
9230
|
}
|
|
9231
|
+
function ignoredLeftOut(report, ignored, declared) {
|
|
9232
|
+
const nameOf = (path) => (path.split("/").at(-1) ?? path).replace(/\.gd$/, "");
|
|
9233
|
+
const leftOut = new Set;
|
|
9234
|
+
const unmatched = [];
|
|
9235
|
+
for (const entry of ignored) {
|
|
9236
|
+
const bare = entry.replace("res://", "");
|
|
9237
|
+
const at = bare.lastIndexOf(":");
|
|
9238
|
+
const suiteNamed = at === -1 ? bare : bare.slice(0, at);
|
|
9239
|
+
const testNamed = at === -1 ? null : bare.slice(at + 1);
|
|
9240
|
+
const named = declared.filter((suite) => nameOf(suite.path) === suiteNamed || suite.path.includes(suiteNamed)).flatMap((suite) => suite.cases.filter((one) => testNamed === null || one === testNamed).map((test) => ({ suite, test })));
|
|
9241
|
+
if (named.length === 0) {
|
|
9242
|
+
unmatched.push(entry);
|
|
9243
|
+
continue;
|
|
9244
|
+
}
|
|
9245
|
+
for (const { suite, test } of named) {
|
|
9246
|
+
const reported = report.suites.filter((ran) => ran.path === suite.path || ran.path === null && ran.name === nameOf(suite.path)).some((ran) => ran.cases.some((one) => one.name === test || new RegExp(`^${test}\\W`).test(one.name)));
|
|
9247
|
+
if (!reported) {
|
|
9248
|
+
leftOut.add(`${nameOf(suite.path)}:${test}`);
|
|
9249
|
+
}
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
return { leftOut: [...leftOut].sort(), unmatched };
|
|
9253
|
+
}
|
|
8747
9254
|
function caseOf(suite, element) {
|
|
8748
9255
|
const of = (kind) => element.children.filter((child) => child.name === kind).map((child) => {
|
|
8749
9256
|
const detail = child.text.trim();
|
|
@@ -9104,7 +9611,7 @@ var init_junit = __esm(() => {
|
|
|
9104
9611
|
});
|
|
9105
9612
|
|
|
9106
9613
|
// src/script-origins.ts
|
|
9107
|
-
import { join as
|
|
9614
|
+
import { join as join12 } from "node:path";
|
|
9108
9615
|
function unresolvedClassIn(message) {
|
|
9109
9616
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
9110
9617
|
}
|
|
@@ -9166,7 +9673,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
9166
9673
|
const ran = await runEngine(godotPath, [
|
|
9167
9674
|
"--headless",
|
|
9168
9675
|
"--log-file",
|
|
9169
|
-
|
|
9676
|
+
join12(logDir, "engine.log"),
|
|
9170
9677
|
"--path",
|
|
9171
9678
|
projectPath,
|
|
9172
9679
|
"--check-only",
|
|
@@ -9212,8 +9719,8 @@ var init_script_origins = __esm(() => {
|
|
|
9212
9719
|
});
|
|
9213
9720
|
|
|
9214
9721
|
// src/headless.ts
|
|
9215
|
-
import { existsSync as
|
|
9216
|
-
import { dirname as dirname7, join as
|
|
9722
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9723
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
9217
9724
|
function snakeCased(params) {
|
|
9218
9725
|
const result = emptyRecord();
|
|
9219
9726
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -9225,8 +9732,10 @@ function snakeCased(params) {
|
|
|
9225
9732
|
function engineExtras(outcome) {
|
|
9226
9733
|
const after = outcome.afterAnswerMessages ?? [];
|
|
9227
9734
|
const before = outcome.beforeOperationMessages ?? [];
|
|
9735
|
+
const notImported = notImportedNote(outcome.messages);
|
|
9228
9736
|
return {
|
|
9229
9737
|
...outcome.messages.length > 0 ? { engine_messages: outcome.messages } : {},
|
|
9738
|
+
...notImported === undefined ? {} : { engine_messages_note: notImported },
|
|
9230
9739
|
...before.length > 0 ? {
|
|
9231
9740
|
engine_messages_before_operation: before,
|
|
9232
9741
|
engine_messages_before_operation_note: withNotCompiled(BEFORE_OPERATION_NOTE, before)
|
|
@@ -9245,6 +9754,20 @@ function withNotCompiled(note, entries) {
|
|
|
9245
9754
|
const path = entries.map(notCompiledIn).find((found) => found !== undefined);
|
|
9246
9755
|
return path === undefined ? note : `${note} ${path} did not compile, so every run of this project starts its autoloads without it until it is fixed; its errors are among these.`;
|
|
9247
9756
|
}
|
|
9757
|
+
function notImportedNote(entries) {
|
|
9758
|
+
const paths = [
|
|
9759
|
+
...new Set(entries.flatMap((entry) => {
|
|
9760
|
+
const path = /^No loader found for resource: (res:\/\/\S+)/.exec(entry.text)?.[1];
|
|
9761
|
+
const extension = path?.split(".").pop()?.toLowerCase() ?? "";
|
|
9762
|
+
return path !== undefined && IMPORTABLE_EXTENSIONS.includes(extension) ? [path] : [];
|
|
9763
|
+
}))
|
|
9764
|
+
];
|
|
9765
|
+
if (paths.length === 0) {
|
|
9766
|
+
return;
|
|
9767
|
+
}
|
|
9768
|
+
const one = paths.length === 1;
|
|
9769
|
+
return `${paths.join(", ")} ${one ? "has" : "have"} no import the engine can load, which is how a file it imports stands before it is first imported, so what uses ${one ? "it" : "them"} reads ${one ? "it" : "them"} as missing although ${one ? "it is" : "they are"} on disk: project_import reimport imports ${one ? "it" : "them"}.`;
|
|
9770
|
+
}
|
|
9248
9771
|
function withoutAsides(entries) {
|
|
9249
9772
|
const kept = [];
|
|
9250
9773
|
let aside = false;
|
|
@@ -9351,7 +9874,7 @@ async function importOnce(godotPath, projectPath) {
|
|
|
9351
9874
|
ran = await runEngine(godotPath, [
|
|
9352
9875
|
"--headless",
|
|
9353
9876
|
"--log-file",
|
|
9354
|
-
|
|
9877
|
+
join13(logDir, "engine.log"),
|
|
9355
9878
|
"--path",
|
|
9356
9879
|
projectPath,
|
|
9357
9880
|
"--import"
|
|
@@ -9373,13 +9896,13 @@ async function importOnce(godotPath, projectPath) {
|
|
|
9373
9896
|
}
|
|
9374
9897
|
async function runOperation(engine, operation, params, projectPath) {
|
|
9375
9898
|
const paramsDir = scratchDirectory("params");
|
|
9376
|
-
const paramsFile =
|
|
9377
|
-
const answerFile =
|
|
9899
|
+
const paramsFile = join13(paramsDir, `${operation}.json`);
|
|
9900
|
+
const answerFile = join13(paramsDir, "answer.json");
|
|
9378
9901
|
writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
9379
9902
|
const args = [
|
|
9380
9903
|
"--headless",
|
|
9381
9904
|
"--log-file",
|
|
9382
|
-
|
|
9905
|
+
join13(paramsDir, "engine.log"),
|
|
9383
9906
|
"--path",
|
|
9384
9907
|
projectPath,
|
|
9385
9908
|
"--script",
|
|
@@ -9467,10 +9990,10 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
9467
9990
|
};
|
|
9468
9991
|
}
|
|
9469
9992
|
function answerIn(file) {
|
|
9470
|
-
if (!
|
|
9993
|
+
if (!existsSync11(file)) {
|
|
9471
9994
|
return { kind: "none" };
|
|
9472
9995
|
}
|
|
9473
|
-
const text =
|
|
9996
|
+
const text = readFileSync7(file, "utf8");
|
|
9474
9997
|
try {
|
|
9475
9998
|
const parsed = JSON.parse(text);
|
|
9476
9999
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
|
|
@@ -9485,6 +10008,7 @@ var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, wh
|
|
|
9485
10008
|
var init_headless = __esm(() => {
|
|
9486
10009
|
init_doc_cache();
|
|
9487
10010
|
init_engine_run();
|
|
10011
|
+
init_import_freshness();
|
|
9488
10012
|
init_library_copies();
|
|
9489
10013
|
init_paths();
|
|
9490
10014
|
init_reimport();
|
|
@@ -9493,9 +10017,9 @@ var init_headless = __esm(() => {
|
|
|
9493
10017
|
});
|
|
9494
10018
|
|
|
9495
10019
|
// src/update-check.ts
|
|
9496
|
-
import { existsSync as
|
|
10020
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9497
10021
|
import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
|
|
9498
|
-
import { join as
|
|
10022
|
+
import { join as join14 } from "node:path";
|
|
9499
10023
|
function packageUrl(root) {
|
|
9500
10024
|
return `${root.replace(/\/+$/, "")}/gdharness/latest`;
|
|
9501
10025
|
}
|
|
@@ -9517,28 +10041,28 @@ function cacheDirectory(environment) {
|
|
|
9517
10041
|
};
|
|
9518
10042
|
const home = set("HOME") ?? homedir4();
|
|
9519
10043
|
if (process.platform === "win32") {
|
|
9520
|
-
return
|
|
10044
|
+
return join14(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
9521
10045
|
}
|
|
9522
10046
|
if (process.platform === "darwin") {
|
|
9523
|
-
return
|
|
10047
|
+
return join14(home, "Library", "Caches", "gdharness");
|
|
9524
10048
|
}
|
|
9525
|
-
return
|
|
10049
|
+
return join14(set("XDG_CACHE_HOME") ?? join14(home, ".cache"), "gdharness");
|
|
9526
10050
|
}
|
|
9527
10051
|
function cacheFile(environment = process.env) {
|
|
9528
10052
|
try {
|
|
9529
10053
|
const directory = cacheDirectory(environment);
|
|
9530
10054
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
9531
|
-
return
|
|
10055
|
+
return join14(directory, "update-check.json");
|
|
9532
10056
|
} catch {
|
|
9533
|
-
return
|
|
10057
|
+
return join14(tmpdir3(), "gdharness-update-check.json");
|
|
9534
10058
|
}
|
|
9535
10059
|
}
|
|
9536
10060
|
function readCache(path) {
|
|
9537
10061
|
try {
|
|
9538
|
-
if (!
|
|
10062
|
+
if (!existsSync12(path)) {
|
|
9539
10063
|
return null;
|
|
9540
10064
|
}
|
|
9541
|
-
const parsed = JSON.parse(
|
|
10065
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
9542
10066
|
if (typeof parsed !== "object" || parsed === null) {
|
|
9543
10067
|
return null;
|
|
9544
10068
|
}
|
|
@@ -9686,7 +10210,7 @@ var init_update_check = __esm(() => {
|
|
|
9686
10210
|
});
|
|
9687
10211
|
|
|
9688
10212
|
// src/server-version.ts
|
|
9689
|
-
import { readFileSync as
|
|
10213
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
9690
10214
|
function staleAddon(addonDigest, shippedDigest) {
|
|
9691
10215
|
return addonDigest !== shippedDigest;
|
|
9692
10216
|
}
|
|
@@ -9745,7 +10269,7 @@ var init_server_version = __esm(() => {
|
|
|
9745
10269
|
DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
|
|
9746
10270
|
SERVER_VERSION = (() => {
|
|
9747
10271
|
try {
|
|
9748
|
-
const pkg = JSON.parse(
|
|
10272
|
+
const pkg = JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8"));
|
|
9749
10273
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
9750
10274
|
} catch {
|
|
9751
10275
|
return "0.0.0";
|
|
@@ -9754,7 +10278,7 @@ var init_server_version = __esm(() => {
|
|
|
9754
10278
|
});
|
|
9755
10279
|
|
|
9756
10280
|
// src/issues.ts
|
|
9757
|
-
import { createHash as
|
|
10281
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9758
10282
|
import process9 from "node:process";
|
|
9759
10283
|
function feedbackNotice(every) {
|
|
9760
10284
|
return JSON.stringify({
|
|
@@ -9773,7 +10297,7 @@ function runtime() {
|
|
|
9773
10297
|
function defectSignature(where, message) {
|
|
9774
10298
|
const normalised = `${where}
|
|
9775
10299
|
${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
|
|
9776
|
-
return
|
|
10300
|
+
return createHash3("sha256").update(normalised).digest("hex").slice(0, 8);
|
|
9777
10301
|
}
|
|
9778
10302
|
function facts(where, message, godotVersion) {
|
|
9779
10303
|
return [
|
|
@@ -10530,8 +11054,8 @@ var init_gdscript_source = __esm(() => {
|
|
|
10530
11054
|
});
|
|
10531
11055
|
|
|
10532
11056
|
// src/class-cache.ts
|
|
10533
|
-
import { existsSync as
|
|
10534
|
-
import { join as
|
|
11057
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync10, statSync as statSync7 } from "node:fs";
|
|
11058
|
+
import { join as join15, posix } from "node:path";
|
|
10535
11059
|
function isToolScript(source) {
|
|
10536
11060
|
return TOOL.test(codeOf(source));
|
|
10537
11061
|
}
|
|
@@ -10550,18 +11074,18 @@ function declaredClassOf(source) {
|
|
|
10550
11074
|
}
|
|
10551
11075
|
function eachScript(projectPath, each) {
|
|
10552
11076
|
const visit = (directory, prefix) => {
|
|
10553
|
-
if (
|
|
11077
|
+
if (existsSync13(join15(directory, ".gdignore"))) {
|
|
10554
11078
|
return;
|
|
10555
11079
|
}
|
|
10556
|
-
for (const entry of
|
|
11080
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
10557
11081
|
if (entry.name.startsWith(".")) {
|
|
10558
11082
|
continue;
|
|
10559
11083
|
}
|
|
10560
|
-
const path =
|
|
11084
|
+
const path = join15(directory, entry.name);
|
|
10561
11085
|
if (entry.isDirectory()) {
|
|
10562
11086
|
visit(path, `${prefix}${entry.name}/`);
|
|
10563
11087
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
10564
|
-
each(`res://${prefix}${entry.name}`,
|
|
11088
|
+
each(`res://${prefix}${entry.name}`, readFileSync10(path, "utf8"));
|
|
10565
11089
|
}
|
|
10566
11090
|
}
|
|
10567
11091
|
};
|
|
@@ -10583,12 +11107,12 @@ function scriptsNaming(projectPath, classes) {
|
|
|
10583
11107
|
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
10584
11108
|
}
|
|
10585
11109
|
function cachedClasses(projectPath) {
|
|
10586
|
-
const cache =
|
|
10587
|
-
if (!
|
|
11110
|
+
const cache = join15(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
11111
|
+
if (!existsSync13(cache)) {
|
|
10588
11112
|
return null;
|
|
10589
11113
|
}
|
|
10590
11114
|
const listed = new Map;
|
|
10591
|
-
const text =
|
|
11115
|
+
const text = readFileSync10(cache, "utf8");
|
|
10592
11116
|
for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
|
|
10593
11117
|
listed.set(entry[1] ?? "", entry[2] ?? "");
|
|
10594
11118
|
}
|
|
@@ -10881,14 +11405,14 @@ function uncachedForTheEngineNote(projectPath, types) {
|
|
|
10881
11405
|
}
|
|
10882
11406
|
const one = types.length === 1;
|
|
10883
11407
|
const named = types.map((type) => `${type.type} (${type.declaredIn})`).join(", ");
|
|
10884
|
-
const cacheExists =
|
|
11408
|
+
const cacheExists = existsSync13(join15(projectPath, ".godot", "global_script_class_cache.cfg"));
|
|
10885
11409
|
const state = cacheExists ? `missing from .godot/global_script_class_cache.cfg, which is where the engine that compiled these scripts resolves a class name from, so the error is the cache being behind the files rather than the script.` : `the engine that compiled these scripts resolves a class name from .godot/global_script_class_cache.cfg, which has not been written yet, as in a project no editor has opened, so the error is the missing cache rather than the script.`;
|
|
10886
11410
|
return `${named} ${one ? "is" : "are"} declared in this project${cacheExists ? " and" : ", but"} ${state} ` + "project_import refresh_classes writes the cache from the declarations on disk; editor_run start " + "writes it before a game, and an editor opening the project writes it when it scans.";
|
|
10887
11411
|
}
|
|
10888
11412
|
function cacheWrittenAt(projectPath) {
|
|
10889
|
-
const cache =
|
|
11413
|
+
const cache = join15(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
10890
11414
|
try {
|
|
10891
|
-
return
|
|
11415
|
+
return statSync7(cache).mtimeMs;
|
|
10892
11416
|
} catch {
|
|
10893
11417
|
return null;
|
|
10894
11418
|
}
|
|
@@ -10900,14 +11424,14 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
10900
11424
|
return false;
|
|
10901
11425
|
}
|
|
10902
11426
|
try {
|
|
10903
|
-
return
|
|
11427
|
+
return statSync7(join15(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
10904
11428
|
} catch {
|
|
10905
11429
|
return false;
|
|
10906
11430
|
}
|
|
10907
11431
|
});
|
|
10908
11432
|
}
|
|
10909
11433
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
10910
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !
|
|
11434
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync13(join15(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
10911
11435
|
}
|
|
10912
11436
|
function staleAgainst(cached, projectPath) {
|
|
10913
11437
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -10931,314 +11455,6 @@ var init_class_cache = __esm(() => {
|
|
|
10931
11455
|
TOOL = /^[ \t]*(?:@[a-z_]+(?:\([^)\n]*\))?[ \t]+)*@tool\b/m;
|
|
10932
11456
|
});
|
|
10933
11457
|
|
|
10934
|
-
// src/project-scan.ts
|
|
10935
|
-
import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "node:fs";
|
|
10936
|
-
import { extname, join as join14 } from "node:path";
|
|
10937
|
-
function skipped(name) {
|
|
10938
|
-
return name.startsWith(".") || SKIPPED.has(name);
|
|
10939
|
-
}
|
|
10940
|
-
function steppedOver(directory) {
|
|
10941
|
-
return gdignored(directory) || installedByUs(directory);
|
|
10942
|
-
}
|
|
10943
|
-
function gdignored(directory) {
|
|
10944
|
-
return existsSync12(join14(directory, ".gdignore"));
|
|
10945
|
-
}
|
|
10946
|
-
function installedByUs(directory) {
|
|
10947
|
-
return existsSync12(join14(directory, VERSION_MARKER));
|
|
10948
|
-
}
|
|
10949
|
-
function projectStructure(projectPath) {
|
|
10950
|
-
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
10951
|
-
const visit = (directory) => {
|
|
10952
|
-
if (steppedOver(directory)) {
|
|
10953
|
-
return;
|
|
10954
|
-
}
|
|
10955
|
-
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10956
|
-
if (skipped(entry.name)) {
|
|
10957
|
-
continue;
|
|
10958
|
-
}
|
|
10959
|
-
if (entry.isDirectory()) {
|
|
10960
|
-
visit(join14(directory, entry.name));
|
|
10961
|
-
} else if (entry.isFile()) {
|
|
10962
|
-
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
10963
|
-
if (SCENE_EXTENSIONS.has(extension)) {
|
|
10964
|
-
structure.scenes += 1;
|
|
10965
|
-
} else if (extension === "gd" || extension === "cs") {
|
|
10966
|
-
structure.scripts += 1;
|
|
10967
|
-
} else if (ASSET_EXTENSIONS.has(extension)) {
|
|
10968
|
-
structure.assets += 1;
|
|
10969
|
-
} else {
|
|
10970
|
-
structure.other += 1;
|
|
10971
|
-
}
|
|
10972
|
-
}
|
|
10973
|
-
}
|
|
10974
|
-
};
|
|
10975
|
-
visit(projectPath);
|
|
10976
|
-
return structure;
|
|
10977
|
-
}
|
|
10978
|
-
function projectFiles(projectPath, extensions) {
|
|
10979
|
-
const found = [];
|
|
10980
|
-
const visit = (directory, prefix) => {
|
|
10981
|
-
if (steppedOver(directory)) {
|
|
10982
|
-
return;
|
|
10983
|
-
}
|
|
10984
|
-
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
10985
|
-
if (skipped(entry.name)) {
|
|
10986
|
-
continue;
|
|
10987
|
-
}
|
|
10988
|
-
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
10989
|
-
if (entry.isDirectory()) {
|
|
10990
|
-
visit(join14(directory, entry.name), spelled);
|
|
10991
|
-
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
10992
|
-
found.push(spelled);
|
|
10993
|
-
}
|
|
10994
|
-
}
|
|
10995
|
-
};
|
|
10996
|
-
visit(projectPath, "");
|
|
10997
|
-
return found.sort();
|
|
10998
|
-
}
|
|
10999
|
-
function scriptsWithoutUid(projectPath) {
|
|
11000
|
-
const found = [];
|
|
11001
|
-
const visit = (directory, prefix) => {
|
|
11002
|
-
if (steppedOver(directory)) {
|
|
11003
|
-
return;
|
|
11004
|
-
}
|
|
11005
|
-
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
11006
|
-
if (skipped(entry.name)) {
|
|
11007
|
-
continue;
|
|
11008
|
-
}
|
|
11009
|
-
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
11010
|
-
if (entry.isDirectory()) {
|
|
11011
|
-
visit(join14(directory, entry.name), spelled);
|
|
11012
|
-
continue;
|
|
11013
|
-
}
|
|
11014
|
-
if (!entry.isFile()) {
|
|
11015
|
-
continue;
|
|
11016
|
-
}
|
|
11017
|
-
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
11018
|
-
if (CARRIES_A_UID.has(extension) && !existsSync12(join14(directory, `${entry.name}.uid`))) {
|
|
11019
|
-
found.push(spelled);
|
|
11020
|
-
}
|
|
11021
|
-
}
|
|
11022
|
-
};
|
|
11023
|
-
visit(projectPath, "");
|
|
11024
|
-
return found.sort();
|
|
11025
|
-
}
|
|
11026
|
-
function pathsInUidCache(projectPath) {
|
|
11027
|
-
let data;
|
|
11028
|
-
try {
|
|
11029
|
-
data = readFileSync9(join14(projectPath, ".godot", "uid_cache.bin"));
|
|
11030
|
-
} catch {
|
|
11031
|
-
return null;
|
|
11032
|
-
}
|
|
11033
|
-
if (data.length < 4) {
|
|
11034
|
-
return null;
|
|
11035
|
-
}
|
|
11036
|
-
const count = data.readUInt32LE(0);
|
|
11037
|
-
const paths = new Map;
|
|
11038
|
-
let at = 4;
|
|
11039
|
-
for (let entry = 0;entry < count; entry++) {
|
|
11040
|
-
if (at + 12 > data.length) {
|
|
11041
|
-
return null;
|
|
11042
|
-
}
|
|
11043
|
-
const id = data.readBigUInt64LE(at);
|
|
11044
|
-
const length = data.readUInt32LE(at + 8);
|
|
11045
|
-
at += 12;
|
|
11046
|
-
if (at + length > data.length) {
|
|
11047
|
-
return null;
|
|
11048
|
-
}
|
|
11049
|
-
paths.set(id, data.toString("utf8", at, at + length));
|
|
11050
|
-
at += length;
|
|
11051
|
-
}
|
|
11052
|
-
return new Set(paths.values());
|
|
11053
|
-
}
|
|
11054
|
-
function isBinary(bytes) {
|
|
11055
|
-
return bytes.subarray(0, 8000).includes(0);
|
|
11056
|
-
}
|
|
11057
|
-
function projectTexts(projectPath) {
|
|
11058
|
-
const found = [];
|
|
11059
|
-
const visit = (directory, prefix) => {
|
|
11060
|
-
if (steppedOver(directory)) {
|
|
11061
|
-
return;
|
|
11062
|
-
}
|
|
11063
|
-
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
11064
|
-
if (skipped(entry.name)) {
|
|
11065
|
-
continue;
|
|
11066
|
-
}
|
|
11067
|
-
const absolute = join14(directory, entry.name);
|
|
11068
|
-
const spelled = `${prefix}${entry.name}`;
|
|
11069
|
-
if (entry.isDirectory()) {
|
|
11070
|
-
visit(absolute, `${spelled}/`);
|
|
11071
|
-
continue;
|
|
11072
|
-
}
|
|
11073
|
-
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
11074
|
-
if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
|
|
11075
|
-
continue;
|
|
11076
|
-
}
|
|
11077
|
-
const bytes = readFileSync9(absolute);
|
|
11078
|
-
if (!isBinary(bytes)) {
|
|
11079
|
-
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
11080
|
-
}
|
|
11081
|
-
}
|
|
11082
|
-
};
|
|
11083
|
-
visit(projectPath, "");
|
|
11084
|
-
return found;
|
|
11085
|
-
}
|
|
11086
|
-
function ignoredTexts(projectPath) {
|
|
11087
|
-
const found = [];
|
|
11088
|
-
const visit = (directory, prefix, ignored) => {
|
|
11089
|
-
if (installedByUs(directory)) {
|
|
11090
|
-
return;
|
|
11091
|
-
}
|
|
11092
|
-
const under = ignored || gdignored(directory);
|
|
11093
|
-
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
11094
|
-
if (skipped(entry.name)) {
|
|
11095
|
-
continue;
|
|
11096
|
-
}
|
|
11097
|
-
const absolute = join14(directory, entry.name);
|
|
11098
|
-
const spelled = `${prefix}${entry.name}`;
|
|
11099
|
-
if (entry.isDirectory()) {
|
|
11100
|
-
visit(absolute, `${spelled}/`, under);
|
|
11101
|
-
continue;
|
|
11102
|
-
}
|
|
11103
|
-
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
11104
|
-
if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
|
|
11105
|
-
continue;
|
|
11106
|
-
}
|
|
11107
|
-
const bytes = readFileSync9(absolute);
|
|
11108
|
-
if (!isBinary(bytes)) {
|
|
11109
|
-
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
11110
|
-
}
|
|
11111
|
-
}
|
|
11112
|
-
};
|
|
11113
|
-
visit(projectPath, "", false);
|
|
11114
|
-
return found;
|
|
11115
|
-
}
|
|
11116
|
-
function plainMatch(line, needle, caseSensitive) {
|
|
11117
|
-
if (caseSensitive) {
|
|
11118
|
-
return line.includes(needle) ? [needle] : null;
|
|
11119
|
-
}
|
|
11120
|
-
const lowered = line.toLowerCase();
|
|
11121
|
-
const at = lowered.indexOf(needle);
|
|
11122
|
-
if (at < 0) {
|
|
11123
|
-
return null;
|
|
11124
|
-
}
|
|
11125
|
-
return [lowered.length === line.length ? line.slice(at, at + needle.length) : needle];
|
|
11126
|
-
}
|
|
11127
|
-
function compiledQuery(query, caseSensitive) {
|
|
11128
|
-
try {
|
|
11129
|
-
return new RegExp(query, caseSensitive ? "" : "i");
|
|
11130
|
-
} catch (error) {
|
|
11131
|
-
const why = errorMessage(error).split(": ").at(-1) ?? "";
|
|
11132
|
-
throw new Error(`'${query}' is not a regular expression: ${why.charAt(0).toLowerCase()}${why.slice(1)}. With regex true, a character such as ( ) [ ] { } . * + ? | ^ $ or \\ stands for itself only after a backslash, as in \\(; or leave regex out to search for the text as written.`);
|
|
11133
|
-
}
|
|
11134
|
-
}
|
|
11135
|
-
function searchProject(projectPath, options) {
|
|
11136
|
-
const extensions = options.fileTypes === undefined ? null : new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
|
|
11137
|
-
const result = {
|
|
11138
|
-
query: options.query,
|
|
11139
|
-
results: [],
|
|
11140
|
-
summary: { files_searched: 0, files_with_matches: 0, total_matches: 0, truncated: false }
|
|
11141
|
-
};
|
|
11142
|
-
const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
|
|
11143
|
-
const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
|
|
11144
|
-
const full = () => result.summary.truncated;
|
|
11145
|
-
const visit = (directory) => {
|
|
11146
|
-
if (steppedOver(directory)) {
|
|
11147
|
-
return;
|
|
11148
|
-
}
|
|
11149
|
-
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
11150
|
-
if (full()) {
|
|
11151
|
-
return;
|
|
11152
|
-
}
|
|
11153
|
-
if (skipped(entry.name)) {
|
|
11154
|
-
continue;
|
|
11155
|
-
}
|
|
11156
|
-
const entryPath = join14(directory, entry.name);
|
|
11157
|
-
if (entry.isDirectory()) {
|
|
11158
|
-
visit(entryPath);
|
|
11159
|
-
continue;
|
|
11160
|
-
}
|
|
11161
|
-
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
11162
|
-
if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
|
|
11163
|
-
continue;
|
|
11164
|
-
}
|
|
11165
|
-
const bytes = readFileSync9(entryPath);
|
|
11166
|
-
if (extensions === null && isBinary(bytes)) {
|
|
11167
|
-
continue;
|
|
11168
|
-
}
|
|
11169
|
-
result.summary.files_searched += 1;
|
|
11170
|
-
const matches = [];
|
|
11171
|
-
for (const [index, line] of bytes.toString("utf8").split(/\r?\n/).entries()) {
|
|
11172
|
-
const found = regex ? regex.exec(line) : plainMatch(line, needle, options.caseSensitive);
|
|
11173
|
-
if (found === null) {
|
|
11174
|
-
continue;
|
|
11175
|
-
}
|
|
11176
|
-
if (result.summary.total_matches >= options.maxResults) {
|
|
11177
|
-
result.summary.truncated = true;
|
|
11178
|
-
break;
|
|
11179
|
-
}
|
|
11180
|
-
matches.push({ line: index + 1, content: line.trim(), match: found[0] });
|
|
11181
|
-
result.summary.total_matches += 1;
|
|
11182
|
-
}
|
|
11183
|
-
if (matches.length > 0) {
|
|
11184
|
-
const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
|
|
11185
|
-
result.results.push({ file: `res://${relativePath}`, matches });
|
|
11186
|
-
result.summary.files_with_matches += 1;
|
|
11187
|
-
}
|
|
11188
|
-
}
|
|
11189
|
-
};
|
|
11190
|
-
visit(projectPath);
|
|
11191
|
-
return result;
|
|
11192
|
-
}
|
|
11193
|
-
var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
|
|
11194
|
-
var init_project_scan = __esm(() => {
|
|
11195
|
-
init_errors();
|
|
11196
|
-
SKIPPED = new Set(["node_modules"]);
|
|
11197
|
-
SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
11198
|
-
ASSET_EXTENSIONS = new Set([
|
|
11199
|
-
"png",
|
|
11200
|
-
"jpg",
|
|
11201
|
-
"jpeg",
|
|
11202
|
-
"webp",
|
|
11203
|
-
"svg",
|
|
11204
|
-
"bmp",
|
|
11205
|
-
"tga",
|
|
11206
|
-
"exr",
|
|
11207
|
-
"hdr",
|
|
11208
|
-
"ktx",
|
|
11209
|
-
"dds",
|
|
11210
|
-
"ttf",
|
|
11211
|
-
"otf",
|
|
11212
|
-
"woff",
|
|
11213
|
-
"woff2",
|
|
11214
|
-
"fnt",
|
|
11215
|
-
"wav",
|
|
11216
|
-
"mp3",
|
|
11217
|
-
"ogg",
|
|
11218
|
-
"glb",
|
|
11219
|
-
"gltf",
|
|
11220
|
-
"obj",
|
|
11221
|
-
"fbx",
|
|
11222
|
-
"blend",
|
|
11223
|
-
"dae",
|
|
11224
|
-
"ogv",
|
|
11225
|
-
"gdshader",
|
|
11226
|
-
"shader"
|
|
11227
|
-
]);
|
|
11228
|
-
CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
11229
|
-
BINARY_EXTENSIONS = new Set([
|
|
11230
|
-
...[...ASSET_EXTENSIONS].filter((extension) => !["svg", "gltf", "gdshader", "shader", "fnt"].includes(extension)),
|
|
11231
|
-
"scn",
|
|
11232
|
-
"res",
|
|
11233
|
-
"zip",
|
|
11234
|
-
"pck",
|
|
11235
|
-
"exe",
|
|
11236
|
-
"dll",
|
|
11237
|
-
"so",
|
|
11238
|
-
"dylib"
|
|
11239
|
-
]);
|
|
11240
|
-
});
|
|
11241
|
-
|
|
11242
11458
|
// src/answer-json.ts
|
|
11243
11459
|
function answerJson(value) {
|
|
11244
11460
|
return JSON.stringify(value, null, 2).replace(SCALAR_ARRAY, (_whole, items) => `[${items.split(/,\n\s+/).join(", ")}]`);
|
|
@@ -11250,7 +11466,7 @@ var init_answer_json = __esm(() => {
|
|
|
11250
11466
|
});
|
|
11251
11467
|
|
|
11252
11468
|
// src/resources.ts
|
|
11253
|
-
import { readFileSync as
|
|
11469
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
11254
11470
|
import { extname as extname2, resolve as resolve3 } from "node:path";
|
|
11255
11471
|
function ensureProjectPath(getProjectPath) {
|
|
11256
11472
|
const projectPath = getProjectPath();
|
|
@@ -11446,7 +11662,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
11446
11662
|
const parsedUri = parseGodotUri(uri);
|
|
11447
11663
|
if (parsedUri.kind === "project-info") {
|
|
11448
11664
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
11449
|
-
const rawProject =
|
|
11665
|
+
const rawProject = readFileSync11(projectFilePath, "utf-8");
|
|
11450
11666
|
const parsedProject = parseProjectGodot(rawProject);
|
|
11451
11667
|
return {
|
|
11452
11668
|
mimeType: "application/json",
|
|
@@ -11455,7 +11671,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
11455
11671
|
}
|
|
11456
11672
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
11457
11673
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
11458
|
-
const text =
|
|
11674
|
+
const text = readFileSync11(filePath, "utf-8");
|
|
11459
11675
|
return {
|
|
11460
11676
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
11461
11677
|
text
|
|
@@ -11596,19 +11812,19 @@ function readParams(params, key) {
|
|
|
11596
11812
|
}
|
|
11597
11813
|
|
|
11598
11814
|
// src/setup.ts
|
|
11599
|
-
import { createHash as
|
|
11815
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
11600
11816
|
import {
|
|
11601
11817
|
cpSync,
|
|
11602
|
-
existsSync as
|
|
11818
|
+
existsSync as existsSync14,
|
|
11603
11819
|
mkdirSync as mkdirSync4,
|
|
11604
|
-
readdirSync as
|
|
11605
|
-
readFileSync as
|
|
11820
|
+
readdirSync as readdirSync9,
|
|
11821
|
+
readFileSync as readFileSync12,
|
|
11606
11822
|
rmdirSync,
|
|
11607
11823
|
rmSync as rmSync5,
|
|
11608
|
-
statSync as
|
|
11824
|
+
statSync as statSync8,
|
|
11609
11825
|
writeFileSync as writeFileSync6
|
|
11610
11826
|
} from "node:fs";
|
|
11611
|
-
import { dirname as dirname8, join as
|
|
11827
|
+
import { dirname as dirname8, join as join16 } from "node:path";
|
|
11612
11828
|
import { fileURLToPath } from "node:url";
|
|
11613
11829
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
11614
11830
|
return addonsDigest(EDITOR_PLUGINS, from);
|
|
@@ -11617,27 +11833,27 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
11617
11833
|
return addonsDigest([RUNTIME_ADDON], from);
|
|
11618
11834
|
}
|
|
11619
11835
|
function addonsDigest(names, from) {
|
|
11620
|
-
const hash =
|
|
11836
|
+
const hash = createHash4("sha256");
|
|
11621
11837
|
const walk = (directory, relative) => {
|
|
11622
|
-
for (const name of
|
|
11838
|
+
for (const name of readdirSync9(directory).sort()) {
|
|
11623
11839
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
11624
11840
|
continue;
|
|
11625
11841
|
}
|
|
11626
|
-
const path =
|
|
11842
|
+
const path = join16(directory, name);
|
|
11627
11843
|
const inside = `${relative}/${name}`;
|
|
11628
|
-
if (
|
|
11844
|
+
if (statSync8(path).isDirectory()) {
|
|
11629
11845
|
walk(path, inside);
|
|
11630
11846
|
} else {
|
|
11631
11847
|
hash.update(`${inside}
|
|
11632
11848
|
`);
|
|
11633
|
-
hash.update(
|
|
11849
|
+
hash.update(readFileSync12(path));
|
|
11634
11850
|
hash.update(`
|
|
11635
11851
|
`);
|
|
11636
11852
|
}
|
|
11637
11853
|
}
|
|
11638
11854
|
};
|
|
11639
11855
|
for (const name of names) {
|
|
11640
|
-
walk(
|
|
11856
|
+
walk(join16(from, name), name);
|
|
11641
11857
|
}
|
|
11642
11858
|
return hash.digest("hex");
|
|
11643
11859
|
}
|
|
@@ -11656,28 +11872,28 @@ function installedRuntimeDigest(projectPath) {
|
|
|
11656
11872
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
11657
11873
|
}
|
|
11658
11874
|
function digestMarker(projectPath, addon) {
|
|
11659
|
-
const marker =
|
|
11660
|
-
if (!
|
|
11875
|
+
const marker = join16(projectPath, "addons", addon, DIGEST_MARKER);
|
|
11876
|
+
if (!existsSync14(marker)) {
|
|
11661
11877
|
return null;
|
|
11662
11878
|
}
|
|
11663
|
-
const said =
|
|
11879
|
+
const said = readFileSync12(marker, "utf8").trim();
|
|
11664
11880
|
return said === "" ? null : said;
|
|
11665
11881
|
}
|
|
11666
11882
|
function shippedAddonsDirectory() {
|
|
11667
|
-
return
|
|
11883
|
+
return join16(dirname8(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
11668
11884
|
}
|
|
11669
11885
|
function shippedOperationsScript() {
|
|
11670
|
-
return
|
|
11886
|
+
return join16(dirname8(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
11671
11887
|
}
|
|
11672
11888
|
function shippedListedRunner() {
|
|
11673
|
-
return
|
|
11889
|
+
return join16(dirname8(fileURLToPath(import.meta.url)), "godot", "tests", "listed.gd");
|
|
11674
11890
|
}
|
|
11675
11891
|
function gdUnit4Unusable(projectPath) {
|
|
11676
11892
|
const marker = "addons/gdUnit4/plugin.cfg";
|
|
11677
|
-
if (!
|
|
11893
|
+
if (!existsSync14(join16(projectPath, ...marker.split("/")))) {
|
|
11678
11894
|
return { missing: true, reason: `gdUnit4 is not installed in this project: no ${marker}` };
|
|
11679
11895
|
}
|
|
11680
|
-
if (!
|
|
11896
|
+
if (!existsSync14(join16(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
|
|
11681
11897
|
return {
|
|
11682
11898
|
missing: false,
|
|
11683
11899
|
reason: `gdUnit4 in this project is older than 6.0, which the shipped runner needs: no ${GDUNIT4_CI_RUNNER}`
|
|
@@ -11688,44 +11904,44 @@ function gdUnit4Unusable(projectPath) {
|
|
|
11688
11904
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
11689
11905
|
const installed = [];
|
|
11690
11906
|
for (const name of ADDONS) {
|
|
11691
|
-
const source =
|
|
11692
|
-
if (!
|
|
11907
|
+
const source = join16(from, name);
|
|
11908
|
+
if (!existsSync14(join16(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
11693
11909
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
11694
11910
|
}
|
|
11695
|
-
const target =
|
|
11696
|
-
const replaced =
|
|
11911
|
+
const target = join16(projectPath, "addons", name);
|
|
11912
|
+
const replaced = existsSync14(target);
|
|
11697
11913
|
rmSync5(target, { recursive: true, force: true });
|
|
11698
11914
|
mkdirSync4(dirname8(target), { recursive: true });
|
|
11699
11915
|
cpSync(source, target, { recursive: true });
|
|
11700
|
-
writeFileSync6(
|
|
11916
|
+
writeFileSync6(join16(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
11701
11917
|
`);
|
|
11702
11918
|
installed.push({ name, path: target, replaced });
|
|
11703
11919
|
}
|
|
11704
|
-
writeFileSync6(
|
|
11920
|
+
writeFileSync6(join16(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
11705
11921
|
`);
|
|
11706
|
-
writeFileSync6(
|
|
11922
|
+
writeFileSync6(join16(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
|
|
11707
11923
|
`);
|
|
11708
11924
|
return installed;
|
|
11709
11925
|
}
|
|
11710
11926
|
function installedAddonVersion(projectPath) {
|
|
11711
|
-
const marker =
|
|
11712
|
-
if (!
|
|
11927
|
+
const marker = join16(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
11928
|
+
if (!existsSync14(marker)) {
|
|
11713
11929
|
return null;
|
|
11714
11930
|
}
|
|
11715
|
-
const said =
|
|
11931
|
+
const said = readFileSync12(marker, "utf8").trim();
|
|
11716
11932
|
return said === "" ? null : said;
|
|
11717
11933
|
}
|
|
11718
11934
|
function removeAddons(projectPath) {
|
|
11719
11935
|
const removed = [];
|
|
11720
11936
|
for (const name of ADDONS) {
|
|
11721
|
-
const target =
|
|
11722
|
-
if (
|
|
11937
|
+
const target = join16(projectPath, "addons", name);
|
|
11938
|
+
if (existsSync14(target)) {
|
|
11723
11939
|
rmSync5(target, { recursive: true, force: true });
|
|
11724
11940
|
removed.push(target);
|
|
11725
11941
|
}
|
|
11726
11942
|
}
|
|
11727
|
-
const addons =
|
|
11728
|
-
if (removed.length > 0 &&
|
|
11943
|
+
const addons = join16(projectPath, "addons");
|
|
11944
|
+
if (removed.length > 0 && existsSync14(addons) && readdirSync9(addons).length === 0) {
|
|
11729
11945
|
rmdirSync(addons);
|
|
11730
11946
|
removed.push(addons);
|
|
11731
11947
|
}
|
|
@@ -11759,7 +11975,7 @@ function loaderAmong(projectPath, named) {
|
|
|
11759
11975
|
}
|
|
11760
11976
|
let source = null;
|
|
11761
11977
|
try {
|
|
11762
|
-
source =
|
|
11978
|
+
source = readFileSync12(join16(projectPath, path), "utf8");
|
|
11763
11979
|
} catch {}
|
|
11764
11980
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
11765
11981
|
return { name: entry, path, exists: true };
|
|
@@ -11801,10 +12017,10 @@ function enabledPlugins(settings) {
|
|
|
11801
12017
|
function inspectProject(projectPath) {
|
|
11802
12018
|
const problems = [];
|
|
11803
12019
|
const addons = ADDONS.map((name) => {
|
|
11804
|
-
const directory =
|
|
11805
|
-
const marker =
|
|
11806
|
-
const installed =
|
|
11807
|
-
const version =
|
|
12020
|
+
const directory = join16(projectPath, "addons", name);
|
|
12021
|
+
const marker = join16(directory, VERSION_MARKER);
|
|
12022
|
+
const installed = existsSync14(directory);
|
|
12023
|
+
const version = existsSync14(marker) ? readFileSync12(marker, "utf8").trim() : null;
|
|
11808
12024
|
const current = version === SERVER_VERSION;
|
|
11809
12025
|
if (!installed) {
|
|
11810
12026
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -11813,7 +12029,7 @@ function inspectProject(projectPath) {
|
|
|
11813
12029
|
}
|
|
11814
12030
|
return { name, installed, version, current };
|
|
11815
12031
|
});
|
|
11816
|
-
const settings = parseProjectGodot(
|
|
12032
|
+
const settings = parseProjectGodot(readFileSync12(join16(projectPath, "project.godot"), "utf8"));
|
|
11817
12033
|
const pluginsEnabled = enabledPlugins(settings);
|
|
11818
12034
|
for (const name of EDITOR_PLUGINS) {
|
|
11819
12035
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -11829,7 +12045,7 @@ function inspectProject(projectPath) {
|
|
|
11829
12045
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
11830
12046
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
11831
12047
|
for (const [name, path] of named) {
|
|
11832
|
-
if (!
|
|
12048
|
+
if (!existsSync14(join16(projectPath, path))) {
|
|
11833
12049
|
if (!addonMissing(path)) {
|
|
11834
12050
|
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`);
|
|
11835
12051
|
}
|
|
@@ -11982,6 +12198,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11982
12198
|
init_launch();
|
|
11983
12199
|
PROJECT_INFO_SECTIONS = [
|
|
11984
12200
|
"autoloads",
|
|
12201
|
+
"classes",
|
|
11985
12202
|
"input_actions",
|
|
11986
12203
|
"plugins",
|
|
11987
12204
|
"export_presets",
|
|
@@ -12027,9 +12244,23 @@ var init_tool_definitions = __esm(() => {
|
|
|
12027
12244
|
};
|
|
12028
12245
|
ANIMATION_TRACK = {
|
|
12029
12246
|
type: "object",
|
|
12030
|
-
description: "
|
|
12247
|
+
description: "add_track: the track to add. remove_track: the track to take out, found by its type, nodePath and property as read answers them; its keyframes are not needed.",
|
|
12031
12248
|
properties: {
|
|
12032
|
-
type: {
|
|
12249
|
+
type: {
|
|
12250
|
+
type: "string",
|
|
12251
|
+
enum: [
|
|
12252
|
+
"property",
|
|
12253
|
+
"method",
|
|
12254
|
+
"position_3d",
|
|
12255
|
+
"rotation_3d",
|
|
12256
|
+
"scale_3d",
|
|
12257
|
+
"blend_shape",
|
|
12258
|
+
"bezier",
|
|
12259
|
+
"audio",
|
|
12260
|
+
"animation"
|
|
12261
|
+
],
|
|
12262
|
+
description: "add_track makes property and method tracks; remove_track takes any type read answers."
|
|
12263
|
+
},
|
|
12033
12264
|
nodePath: { type: "string", description: "Target node, relative to the AnimationPlayer root." },
|
|
12034
12265
|
property: { type: "string", description: "For property tracks: the property to animate." },
|
|
12035
12266
|
method: {
|
|
@@ -12038,6 +12269,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12038
12269
|
},
|
|
12039
12270
|
keyframes: {
|
|
12040
12271
|
type: "array",
|
|
12272
|
+
description: "add_track: the keys to add, each at its own time.",
|
|
12041
12273
|
items: {
|
|
12042
12274
|
type: "object",
|
|
12043
12275
|
properties: {
|
|
@@ -12050,7 +12282,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12050
12282
|
}
|
|
12051
12283
|
}
|
|
12052
12284
|
},
|
|
12053
|
-
required: ["type", "nodePath"
|
|
12285
|
+
required: ["type", "nodePath"],
|
|
12054
12286
|
additionalProperties: false
|
|
12055
12287
|
};
|
|
12056
12288
|
SCRIPT_MODIFICATIONS = {
|
|
@@ -12090,7 +12322,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12090
12322
|
};
|
|
12091
12323
|
INPUT_EVENTS = {
|
|
12092
12324
|
type: "array",
|
|
12093
|
-
description: "The events that trigger the action.",
|
|
12325
|
+
description: "The events that trigger the action, each bound to every device, as the editor binds them. A read names device only on an event bound to one device.",
|
|
12094
12326
|
items: {
|
|
12095
12327
|
type: "object",
|
|
12096
12328
|
properties: {
|
|
@@ -12101,7 +12333,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
12101
12333
|
axisValue: { type: "number", description: "Joypad axes: -1 or 1." },
|
|
12102
12334
|
ctrl: { type: "boolean", description: "Keys: bind it with Ctrl held." },
|
|
12103
12335
|
alt: { type: "boolean", description: "Keys: bind it with Alt held." },
|
|
12104
|
-
shift: { type: "boolean", description: "Keys: bind it with Shift held." }
|
|
12336
|
+
shift: { type: "boolean", description: "Keys: bind it with Shift held." },
|
|
12337
|
+
physical: {
|
|
12338
|
+
type: "boolean",
|
|
12339
|
+
description: "Keys: bind the key by where it sits on the keyboard (physical_keycode) rather than by what it types, as the editor offers, so W stays the key above S on any layout. Default false."
|
|
12340
|
+
}
|
|
12105
12341
|
},
|
|
12106
12342
|
required: ["type"],
|
|
12107
12343
|
additionalProperties: false
|
|
@@ -12149,13 +12385,13 @@ var init_tool_definitions = __esm(() => {
|
|
|
12149
12385
|
name: "project_info",
|
|
12150
12386
|
title: "Project info",
|
|
12151
12387
|
effect: "read",
|
|
12152
|
-
description: "What a project is: its name and main scene from project.godot, the Godot that answers, and how many scenes, scripts and assets it holds, other counting every remaining file, leaving out the addons gdharness installed and any folder holding a .gdignore, with optional sections on top.",
|
|
12388
|
+
description: "What a project is: its name and main scene from project.godot, the Godot that answers, and how many scenes, scripts and assets it holds, other counting every remaining file, leaving out the .import and .uid files the engine writes beside others, the addons gdharness installed and any folder holding a .gdignore, with optional sections on top.",
|
|
12153
12389
|
parameters: {
|
|
12154
12390
|
projectPath: PROJECT_PATH,
|
|
12155
12391
|
include: {
|
|
12156
12392
|
type: "array",
|
|
12157
12393
|
items: { type: "string", enum: [...PROJECT_INFO_SECTIONS] },
|
|
12158
|
-
description: "Extra sections: registered autoloads, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, and each file a scene, resource or script refers to that does not exist."
|
|
12394
|
+
description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, and each file a scene, resource or script refers to that does not exist."
|
|
12159
12395
|
},
|
|
12160
12396
|
preset: { type: "string", description: "For validation: the export preset to validate against." },
|
|
12161
12397
|
detail: {
|
|
@@ -12169,12 +12405,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
12169
12405
|
{
|
|
12170
12406
|
name: "project_settings",
|
|
12171
12407
|
title: "Project settings",
|
|
12172
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. project_info include lists the autoloads, input actions and audio buses as the project holds them. 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, or one on another release's addon that was not asked, 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.",
|
|
12408
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. project_info include lists the autoloads, input actions and audio buses as the project holds them. 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, or one on another release's addon that was not asked, 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 its deadzone when one is given, 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. unset takes a setting's line out of project.godot, the way back from a set of a misspelt name, and answers removed, old_value and default_value, the value that holds now (null for a setting the engine does not declare); on one the file does not set it answers removed false and writes nothing. 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.",
|
|
12173
12409
|
parameters: {
|
|
12174
12410
|
projectPath: PROJECT_PATH,
|
|
12175
12411
|
setting: {
|
|
12176
12412
|
type: "string",
|
|
12177
|
-
ops: ["get", "set"],
|
|
12413
|
+
ops: ["get", "set", "unset"],
|
|
12178
12414
|
description: 'Setting path, such as "display/window/size/viewport_width".'
|
|
12179
12415
|
},
|
|
12180
12416
|
from: {
|
|
@@ -12221,7 +12457,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12221
12457
|
ops: ["add_input_action"],
|
|
12222
12458
|
minimum: 0,
|
|
12223
12459
|
maximum: 1,
|
|
12224
|
-
description: "Input actions: analogue deadzone, 0 to 1. Default 0.5."
|
|
12460
|
+
description: "Input actions: analogue deadzone, 0 to 1. Default: the deadzone of the action being rebound, or 0.5 for a new one."
|
|
12225
12461
|
},
|
|
12226
12462
|
pluginName: {
|
|
12227
12463
|
type: "string",
|
|
@@ -12255,6 +12491,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
12255
12491
|
operations: {
|
|
12256
12492
|
get: { effect: "read", summary: "read one setting, or every setting under prefix", requires: [] },
|
|
12257
12493
|
set: { effect: "destructive", summary: "write one setting", requires: ["setting", "value"] },
|
|
12494
|
+
unset: {
|
|
12495
|
+
effect: "destructive",
|
|
12496
|
+
summary: "take a setting out of project.godot, so the engine default holds",
|
|
12497
|
+
requires: ["setting"]
|
|
12498
|
+
},
|
|
12258
12499
|
add_autoload: {
|
|
12259
12500
|
effect: "additive",
|
|
12260
12501
|
summary: "register an autoload singleton",
|
|
@@ -12300,7 +12541,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12300
12541
|
name: "project_search",
|
|
12301
12542
|
title: "Search the project",
|
|
12302
12543
|
effect: "read",
|
|
12303
|
-
description: "Searches text or a regular expression across project files and returns file paths with line numbers. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs. The addons gdharness installed are not searched either, since nobody on the project wrote them.",
|
|
12544
|
+
description: "Searches text or a regular expression across project files and returns file paths with line numbers. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore, which not_searched names when the search passed one. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs. The addons gdharness installed are not searched either, since nobody on the project wrote them.",
|
|
12304
12545
|
parameters: {
|
|
12305
12546
|
projectPath: PROJECT_PATH,
|
|
12306
12547
|
query: { type: "string", description: "The text or pattern to find." },
|
|
@@ -12326,7 +12567,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12326
12567
|
direction: {
|
|
12327
12568
|
type: "string",
|
|
12328
12569
|
enum: ["forward", "reverse"],
|
|
12329
|
-
description: "forward: what this resource loads, with cycles reported, a script's dependencies including every script whose class_name it uses, marked by_class with the name. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name. Each reference carries a kind: ext_resource, extends, preload, load or path for a reference by path; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about. Two kinds are mentions rather than uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs, and comment is one in ordinary prose, which it would only make stale. summary.in_code is the count with both of those left out, which is the number to read when the question is whether anything still uses this. Default forward."
|
|
12570
|
+
description: "forward: what this resource loads, with cycles reported, a script's dependencies including every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load or path for a reference by path; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Two kinds are mentions rather than uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs, and comment is one in ordinary prose, which it would only make stale. summary.in_code is the count with both of those left out, which is the number to read when the question is whether anything still uses this. Default forward."
|
|
12330
12571
|
},
|
|
12331
12572
|
depth: { type: "number", description: "forward: how many levels to follow. Default unlimited." },
|
|
12332
12573
|
includeBuiltin: {
|
|
@@ -12356,7 +12597,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12356
12597
|
options: {
|
|
12357
12598
|
type: "object",
|
|
12358
12599
|
ops: ["set_options"],
|
|
12359
|
-
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. One already holding the value given is answered under unchanged_options rather than updated_options, and when none changed nothing is written or reimported.`,
|
|
12600
|
+
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. One already holding the value given is answered under unchanged_options rather than updated_options, and when none changed nothing is written or reimported. changed gives each option written as {was, now}, was null for one the import file did not hold.`,
|
|
12360
12601
|
additionalProperties: true
|
|
12361
12602
|
},
|
|
12362
12603
|
reimport: {
|
|
@@ -12414,7 +12655,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12414
12655
|
{
|
|
12415
12656
|
name: "project_export",
|
|
12416
12657
|
title: "Export",
|
|
12417
|
-
description: "Export presets and exports. add_preset writes a preset to export_presets.cfg with the keys the editor's Project > Export writes for a new one, every platform option left at its default, and makes it the platform's runnable preset when the platform has none. It answers where this engine's export templates belong and whether the preset's platform has them, since an export needs them; list says the same for every preset. An editor open on the project does not see a preset added this way until it restarts. run names, as alsoWritten, what else the export wrote beside its output, such as the .pck a Windows or Linux build cannot start without.",
|
|
12658
|
+
description: "Export presets and exports. add_preset writes a preset to export_presets.cfg with the keys the editor's Project > Export writes for a new one, every platform option left at its default, and makes it the platform's runnable preset when the platform has none. It answers where this engine's export templates belong and whether the preset's platform has them, since an export needs them; list says the same for every preset. An editor open on the project does not see a preset added this way until it restarts. run names, as alsoWritten, what else the export wrote beside its output, such as the .pck a Windows or Linux build cannot start without. Its entries are the errors and warnings the export printed, the newest two hundred distinct ones, one printed more than once carrying times, and errors and warnings count them; exported is true only when this run wrote the output and printed no error.",
|
|
12418
12659
|
parameters: {
|
|
12419
12660
|
projectPath: PROJECT_PATH,
|
|
12420
12661
|
preset: {
|
|
@@ -12450,8 +12691,8 @@ var init_tool_definitions = __esm(() => {
|
|
|
12450
12691
|
name: "project_test",
|
|
12451
12692
|
title: "Run tests",
|
|
12452
12693
|
effect: "additive",
|
|
12453
|
-
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, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed. 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. What changed on disk since it was imported is then imported before any suite runs, and named under importedFirst. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. A run that found nothing to run is never called a pass, 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. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. 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
|
|
12454
|
-
answer: "time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, with the suite and the case that printed it, and declaredNotPrinted each declared error its case did not print. Either fails the run, and each suite holding one is listed with its count. engineErrors counts every engine error the run printed but those a suite declared. " + IMPORTED_FIRST,
|
|
12694
|
+
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, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed. 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. What changed on disk since it was imported is then imported before any suite runs, and named under importedFirst. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. A run that found nothing to run is never called a pass, 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. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. 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 orphan nodes were left 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 is not listed there, and none are when the project turns off gdUnit4's report/godot/script_error setting; a suite declares them all the same. Needs gdUnit4 under addons/gdUnit4.",
|
|
12695
|
+
answer: "passed is the verdict to read. exitCode is the engine's own exit, gdUnit4's code for what it saw, and is 0 beside passed false when the run is failed for something gdUnit4 does not judge, such as an engine error no suite declared. time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, with the suite and the case that printed it, and declaredNotPrinted each declared error its case did not print. A declared text must be the whole error after ERROR: and its space, so a longer error its case printed holding the text is named under printedInstead. Either fails the run, and each suite holding one is listed with its count. engineErrors counts every engine error the run printed but those a suite declared. " + IMPORTED_FIRST,
|
|
12455
12696
|
parameters: {
|
|
12456
12697
|
projectPath: PROJECT_PATH,
|
|
12457
12698
|
path: {
|
|
@@ -12462,7 +12703,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12462
12703
|
ignore: {
|
|
12463
12704
|
type: "array",
|
|
12464
12705
|
items: { type: "string" },
|
|
12465
|
-
description: 'Suites or cases to leave out, as "suite_name" or "suite_name:test_name". A bare test name is read as a suite name and leaves out nothing; an entry that named a case which ran all the same comes back under ignoreNotApplied with the form that would have left it out.'
|
|
12706
|
+
description: 'Suites or cases to leave out, as "suite_name" or "suite_name:test_name". A bare test name is read as a suite name and leaves out nothing; an entry that named a case which ran all the same comes back under ignoreNotApplied with the form that would have left it out. Every case the list left out comes back under ignored as suite_name:test_name, so a run called passed says what it did not run, and an entry naming no suite or case under path comes back under ignoreUnmatched.'
|
|
12466
12707
|
},
|
|
12467
12708
|
tests: {
|
|
12468
12709
|
type: "array",
|
|
@@ -12493,7 +12734,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12493
12734
|
{
|
|
12494
12735
|
name: "scene_create",
|
|
12495
12736
|
title: "Create or save a scene",
|
|
12496
|
-
description: `Creates a scene file where none is, saves one, or saves a copy under a new path. create makes a folder the path names that is not there, and names it under madeFolder. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
|
|
12737
|
+
description: `Creates a scene file where none is, saves one, or saves a copy under a new path. create makes a folder the path names that is not there, and names it under madeFolder; its answer names the root's type and name, and under script the script the root carries. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
|
|
12497
12738
|
parameters: {
|
|
12498
12739
|
projectPath: PROJECT_PATH,
|
|
12499
12740
|
scenePath: SCENE_PATH,
|
|
@@ -12531,7 +12772,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12531
12772
|
name: "scene_tree",
|
|
12532
12773
|
title: "Scene tree",
|
|
12533
12774
|
effect: "read",
|
|
12534
|
-
description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). A node in any group answers groups, sorted, from this file and every scene it comes from. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
|
|
12775
|
+
description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). The root of a scene inheriting another names the scene it inherits under inherits, and every node that scene or one further down makes names its file under inheritedFrom; this file holds only the values it changes on them. A node in any group answers groups, sorted, from this file and every scene it comes from. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
|
|
12535
12776
|
parameters: {
|
|
12536
12777
|
projectPath: PROJECT_PATH,
|
|
12537
12778
|
scenePath: SCENE_PATH,
|
|
@@ -12547,7 +12788,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12547
12788
|
name: "scene_node",
|
|
12548
12789
|
title: "Scene nodes",
|
|
12549
12790
|
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, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own, and answers each rewrite under retargeted and each path it left under notRetargeted. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A delete rewrites no path, and answers under stillNaming what named the deleted node and now names nothing. 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. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
|
|
12550
|
-
answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others. Each of these lists is answered only when it names anything. alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}.",
|
|
12791
|
+
answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}.",
|
|
12551
12792
|
parameters: {
|
|
12552
12793
|
projectPath: PROJECT_PATH,
|
|
12553
12794
|
scenePath: SCENE_PATH,
|
|
@@ -12595,7 +12836,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12595
12836
|
keepGlobalTransform: {
|
|
12596
12837
|
type: "boolean",
|
|
12597
12838
|
ops: ["reparent"],
|
|
12598
|
-
description: "reparent: whether the node stays where it is drawn, its transform worked out again under the new parent, as the editor's reparent and Node.reparent keep it. A Control keeps its position only. Default true; false keeps its own transform, so it moves with the new parent."
|
|
12839
|
+
description: "reparent: whether the node stays where it is drawn, its transform worked out again under the new parent, as the editor's reparent and Node.reparent keep it. A Control keeps its position only. A Node3D kept in place under a parent scaled unevenly across its turn ends up sheared, which no position, rotation and scale describe, and the answer then carries its transform under alsoChanged and says so under sheared. Default true; false keeps its own transform, so it moves with the new parent."
|
|
12599
12840
|
},
|
|
12600
12841
|
index: {
|
|
12601
12842
|
type: "integer",
|
|
@@ -12698,18 +12939,18 @@ var init_tool_definitions = __esm(() => {
|
|
|
12698
12939
|
{
|
|
12699
12940
|
name: "scene_animation",
|
|
12700
12941
|
title: "Animations",
|
|
12701
|
-
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. The player and the tree are nodes, so scene_node add puts one in the scene first. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. add_track on a path the animation already has a track of that type for adds the keys to that track, as the editor keys a property it already animates, answering existingTrack true and keysAdded beside keys, the track's count; a key at a time that track already has a key at is refused. A key past the animation's end, where it never plays, is written and named under pastLength. remove clears the player's autoplay when it named the animation and names under statesPlayingNothing the states that played it. read and read_states need no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without running the scene's scripts. The ops that write go 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. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads 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.",
|
|
12942
|
+
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. The player and the tree are nodes, so scene_node add puts one in the scene first. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. add_track on a path the animation already has a track of that type for adds the keys to that track, as the editor keys a property it already animates, answering existingTrack true and keysAdded beside keys, the track's count; a key at a time that track already has a key at is refused. A key past the animation's end, where it never plays, is written and named under pastLength. remove_track takes out every track of the type, nodePath and property given, with its keys. remove clears the player's autoplay when it named the animation and names under statesPlayingNothing the states that played it, which stay in their trees playing nothing until remove_state takes them out. Every state machine has Start and End, which cannot be added or removed; a transition from Start sets the state it begins in. read and read_states need no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without running the scene's scripts. The ops that write go 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. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads 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.",
|
|
12702
12943
|
parameters: {
|
|
12703
12944
|
projectPath: PROJECT_PATH,
|
|
12704
12945
|
scenePath: SCENE_PATH,
|
|
12705
12946
|
playerNodePath: {
|
|
12706
12947
|
type: "string",
|
|
12707
|
-
ops: ["read", "create", "add_track", "set", "rename", "remove"],
|
|
12948
|
+
ops: ["read", "create", "add_track", "remove_track", "set", "rename", "remove"],
|
|
12708
12949
|
description: "The AnimationPlayer node."
|
|
12709
12950
|
},
|
|
12710
12951
|
animationName: {
|
|
12711
12952
|
type: "string",
|
|
12712
|
-
ops: ["read", "create", "add_track", "set", "rename", "remove", "add_state"],
|
|
12953
|
+
ops: ["read", "create", "add_track", "remove_track", "set", "rename", "remove", "add_state"],
|
|
12713
12954
|
description: "The animation in the player. read: the one to answer with whole, its tracks a list; without it, read lists them, each with the number of its tracks under tracks. add_state: the one that state plays. One the player does not have yet is written all the same, answering animationFound false and why under animationMissing."
|
|
12714
12955
|
},
|
|
12715
12956
|
newName: {
|
|
@@ -12733,31 +12974,31 @@ var init_tool_definitions = __esm(() => {
|
|
|
12733
12974
|
ops: ["create", "set"],
|
|
12734
12975
|
description: "create and set: keyframe snap in seconds, 0 or more. create: default 0.1."
|
|
12735
12976
|
},
|
|
12736
|
-
track: { ...ANIMATION_TRACK, ops: ["add_track"] },
|
|
12977
|
+
track: { ...ANIMATION_TRACK, ops: ["add_track", "remove_track"] },
|
|
12737
12978
|
animTreePath: {
|
|
12738
12979
|
type: "string",
|
|
12739
|
-
ops: ["read_states", "add_state", "connect_states"],
|
|
12980
|
+
ops: ["read_states", "add_state", "remove_state", "connect_states", "disconnect_states"],
|
|
12740
12981
|
description: "The AnimationTree node."
|
|
12741
12982
|
},
|
|
12742
12983
|
stateName: {
|
|
12743
12984
|
type: "string",
|
|
12744
|
-
ops: ["add_state"],
|
|
12745
|
-
description: "add_state: the state to add. It goes right of the rightmost state in the editor's graph, answered under position."
|
|
12985
|
+
ops: ["add_state", "remove_state"],
|
|
12986
|
+
description: "add_state: the state to add. It goes right of the rightmost state in the editor's graph, answered under position. remove_state: the state to take out, with every transition leaving or reaching it, named under transitionsRemoved."
|
|
12746
12987
|
},
|
|
12747
12988
|
stateMachinePath: {
|
|
12748
12989
|
type: "string",
|
|
12749
|
-
ops: ["read_states", "add_state", "connect_states"],
|
|
12990
|
+
ops: ["read_states", "add_state", "remove_state", "connect_states", "disconnect_states"],
|
|
12750
12991
|
description: "A nested state machine, by the names of the states that lead to it. Default the root."
|
|
12751
12992
|
},
|
|
12752
12993
|
fromState: {
|
|
12753
12994
|
type: "string",
|
|
12754
|
-
ops: ["connect_states"],
|
|
12755
|
-
description: "connect_states: the state the transition leaves."
|
|
12995
|
+
ops: ["connect_states", "disconnect_states"],
|
|
12996
|
+
description: "connect_states and disconnect_states: the state the transition leaves."
|
|
12756
12997
|
},
|
|
12757
12998
|
toState: {
|
|
12758
12999
|
type: "string",
|
|
12759
|
-
ops: ["connect_states"],
|
|
12760
|
-
description: "connect_states: the state it arrives at."
|
|
13000
|
+
ops: ["connect_states", "disconnect_states"],
|
|
13001
|
+
description: "connect_states and disconnect_states: the state it arrives at."
|
|
12761
13002
|
},
|
|
12762
13003
|
transitionType: {
|
|
12763
13004
|
type: "string",
|
|
@@ -12793,6 +13034,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
12793
13034
|
summary: "a property or method track with keyframes",
|
|
12794
13035
|
requires: ["playerNodePath", "animationName", "track"]
|
|
12795
13036
|
},
|
|
13037
|
+
remove_track: {
|
|
13038
|
+
effect: "destructive",
|
|
13039
|
+
summary: "a track and its keys from an animation",
|
|
13040
|
+
requires: ["playerNodePath", "animationName", "track"]
|
|
13041
|
+
},
|
|
12796
13042
|
set: {
|
|
12797
13043
|
effect: "destructive",
|
|
12798
13044
|
summary: "an animation's length, loop mode or step",
|
|
@@ -12813,10 +13059,20 @@ var init_tool_definitions = __esm(() => {
|
|
|
12813
13059
|
summary: "a state playing an animation, in an AnimationTree",
|
|
12814
13060
|
requires: ["animTreePath", "stateName", "animationName"]
|
|
12815
13061
|
},
|
|
13062
|
+
remove_state: {
|
|
13063
|
+
effect: "destructive",
|
|
13064
|
+
summary: "a state and its transitions from an AnimationTree",
|
|
13065
|
+
requires: ["animTreePath", "stateName"]
|
|
13066
|
+
},
|
|
12816
13067
|
connect_states: {
|
|
12817
13068
|
effect: "additive",
|
|
12818
13069
|
summary: "a transition between two states",
|
|
12819
13070
|
requires: ["animTreePath", "fromState", "toState"]
|
|
13071
|
+
},
|
|
13072
|
+
disconnect_states: {
|
|
13073
|
+
effect: "destructive",
|
|
13074
|
+
summary: "the transition from one state to another",
|
|
13075
|
+
requires: ["animTreePath", "fromState", "toState"]
|
|
12820
13076
|
}
|
|
12821
13077
|
}
|
|
12822
13078
|
},
|
|
@@ -12880,7 +13136,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12880
13136
|
{
|
|
12881
13137
|
name: "script_info",
|
|
12882
13138
|
title: "Script info",
|
|
12883
|
-
description: "What a script contains: its structure from the file, or symbols, completions and hover text from the editor's language server.",
|
|
13139
|
+
description: "What a script contains: its structure from the file, or symbols, completions and hover text from the editor's language server. It reads .gd files only, and answers what a script uses rather than what uses it: project_search finds where a class, a member or a signal is named.",
|
|
12884
13140
|
parameters: {
|
|
12885
13141
|
projectPath: PROJECT_PATH,
|
|
12886
13142
|
scriptPath: SCRIPT_PATH,
|
|
@@ -12906,7 +13162,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12906
13162
|
operations: {
|
|
12907
13163
|
structure: {
|
|
12908
13164
|
effect: "read",
|
|
12909
|
-
summary: "functions, variables (static ones marked is_static; one written with := has the type of a literal or built-in constructor it is given, marked inferred, as a constant always does), signals, constants, enums, inner_classes (each with its name, line and extends, and its own members in these same fields), class_name, extends and line_count, 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",
|
|
13165
|
+
summary: "functions, variables (static ones marked is_static; one written with := has the type of a literal or built-in constructor it is given, marked inferred, as a constant always does), signals, constants, enums, inner_classes (each with its name, line and extends, and its own members in these same fields), class_name, extends and line_count, 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. A member or inner class with a ## documentation comment directly above it carries the comment's text as doc, and the script's own, the ## block above class_name or extends or one followed by a blank line before any member, comes back as class_doc",
|
|
12910
13166
|
requires: []
|
|
12911
13167
|
},
|
|
12912
13168
|
symbols: {
|
|
@@ -12941,7 +13197,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12941
13197
|
{
|
|
12942
13198
|
name: "resource_edit",
|
|
12943
13199
|
title: "Edit resources",
|
|
12944
|
-
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, with resourceType the engine class and scriptClass the class_name of its script when it declares one, and a Theme lists its items there as "Button/colors/font_color". A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. A property holding a resource takes its res:// path; a file never imported is refused with the reimport that imports it. 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.',
|
|
13200
|
+
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, with resourceType the engine class and scriptClass the class_name of its script when it declares one, and a Theme lists its items there as "Button/colors/font_color". create and modify answer each property given under properties as the file holds it once saved, in the same form. A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. A property holding a resource takes its res:// path; a file never imported is refused with the reimport that imports it. 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.',
|
|
12945
13201
|
parameters: {
|
|
12946
13202
|
projectPath: PROJECT_PATH,
|
|
12947
13203
|
resourcePath: {
|
|
@@ -13228,7 +13484,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13228
13484
|
{
|
|
13229
13485
|
name: "editor_output",
|
|
13230
13486
|
title: "Game and editor output",
|
|
13231
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here.
|
|
13487
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. A run answers with counts and a verdict: clean is true when no error was reported, and warnings, counted beside it, do not make it false. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one reported a game as playing fifteen seconds after its process was ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, where the output worth reading usually is, since the class cache, the plugins and the language server speak before any harness connects. It answers counts with no clean, since an editor neither passes nor fails, the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
|
|
13232
13488
|
answer: "Each entry's source says where it was read: transcript for a line of the run's transcript file, which holds both its streams and is how every run with one is read; stdout or stderr for a line of that stream of a run with no transcript, such as an editor-played run picked up after a reconnect whose first server left no note; debugger for what the editor's debug adapter reported and the game never printed, such as the script error a game stopped on.",
|
|
13233
13489
|
parameters: {
|
|
13234
13490
|
projectPath: {
|
|
@@ -13294,7 +13550,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13294
13550
|
name: "editor_rescan",
|
|
13295
13551
|
title: "Rescan the project",
|
|
13296
13552
|
effect: "additive",
|
|
13297
|
-
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it, and the
|
|
13553
|
+
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it. waitedMs is the whole call, and waitedForEditorScanMs, given only when a scan or an import was already running, the part spent waiting it out. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
|
|
13298
13554
|
parameters: {
|
|
13299
13555
|
projectPath: PROJECT_PATH,
|
|
13300
13556
|
timeoutMs: {
|
|
@@ -13456,7 +13712,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13456
13712
|
tree: { effect: "read", summary: "the live scene tree", requires: [] },
|
|
13457
13713
|
text: {
|
|
13458
13714
|
effect: "read",
|
|
13459
|
-
summary: "every line of text under nodePath, the values in its fields included, in reading order, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without
|
|
13715
|
+
summary: "every line of text under nodePath, the values in its fields included, in reading order, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without tags, text only as far as it is typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items and the titles of its separators read a line each",
|
|
13460
13716
|
requires: []
|
|
13461
13717
|
},
|
|
13462
13718
|
find: {
|
|
@@ -13476,7 +13732,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13476
13732
|
},
|
|
13477
13733
|
metrics: {
|
|
13478
13734
|
effect: "read",
|
|
13479
|
-
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of
|
|
13735
|
+
summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of the last frames_timed frames (up to 120; the game, physics and the wait for the display included), frame_time_max the slowest, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. process_frames and physics_frames count frames and physics steps since the start; get_frames_drawn() stays 0 headless. physics_steps_per_second is the physics steps a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, set once a second: the last second's frames and its slowest process and physics step, so they trail a change by up to a second, and the last two are worst cases, not the frame. Memory, object and render counts are read as asked. memory_static is the engine's CPU-side allocation in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are the renderer's GPU memory in bytes, which memory_static leaves out",
|
|
13480
13736
|
requires: []
|
|
13481
13737
|
},
|
|
13482
13738
|
accessibility: {
|
|
@@ -13490,7 +13746,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13490
13746
|
{
|
|
13491
13747
|
name: "runtime_invoke",
|
|
13492
13748
|
title: "Set or call in the running game",
|
|
13493
|
-
description:
|
|
13749
|
+
description: 'Sets a property or calls a method on a node, or on one of the engine singletons nodePath names, in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. A method that awaits is answered with the value it returns once it ends, and awaited true; elapsed_usec then runs to its end, and one that outlasts timeout is collected with op result. Errors the engine reported while a called method ran come back under errors and warnings under warnings, the first 20 of each, with errors_not_shown and warnings_not_shown counting the rest. A script error ends the function it is raised in, which then returns the default of its return type, 0, "" or null, so one that ended the called method itself is named under ended_by_error_at, and result is not a value the method returned. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes. A game this server started that its debugger holds is said so, with where, before a call and under heldAt when a call reached a breakpoint.',
|
|
13494
13750
|
parameters: {
|
|
13495
13751
|
projectPath: RUNNING_PROJECT_PATH,
|
|
13496
13752
|
pid: RUNNING_PID,
|
|
@@ -13615,7 +13871,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13615
13871
|
name: "runtime_input",
|
|
13616
13872
|
title: "Send input to the game",
|
|
13617
13873
|
description: "Input to the running game: a whole click on a Control or a 3D node named by path or on a control named by the words on it, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button, mouse motion or drag. All of it works headless, where the viewport is the project's window size and the GUI only takes what is inside it. On gdharness's own desktop, where a windowed run goes, Windows refuses to say where the pointer is: every pointer answer there carries pointer_on_hidden_desktop, and the first of the run pointer_note saying why it matters: get_mouse_position() on the root window, and the mouse positions derived from it, read a meaningless position, while what reads the event itself (the control under it, hover, tooltips, input handlers) gets the injected position. A native window no real pointer has entered gets no hover from Godot, so the runtime supplies it there and the answers say hover_supplied.",
|
|
13618
|
-
answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again and pressed whether it is left held down (true only after pressed true), for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device.
|
|
13874
|
+
answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again and pressed whether it is left held down (true only after pressed true), for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. key, mouse_click and click answer actions, the InputMap actions the event also presses. mouse_click answers hovered, the control under the point afterwards; the other events are aimed at nothing. A click that did not land on a control whose mouse_filter is ignore, as a Label's is, says so under why_not_landed. An action or a key answers focused with the path of the Control holding the keyboard focus when it went in, since that Control gets it before the game's _unhandled_input: Space or Enter on a focused button presses the button. A mouse_click, mouse_motion or mouse_drag with a point outside the window's viewport says so under outside_viewport, with the viewport's rectangle, since no control there receives it.`,
|
|
13619
13875
|
parameters: {
|
|
13620
13876
|
projectPath: RUNNING_PROJECT_PATH,
|
|
13621
13877
|
pid: RUNNING_PID,
|
|
@@ -13632,7 +13888,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13632
13888
|
action: {
|
|
13633
13889
|
type: "string",
|
|
13634
13890
|
ops: ["action"],
|
|
13635
|
-
description: "action: the InputMap action name. An engine dialog is not answered this way: AcceptDialog reads the Escape key itself and never asks the InputMap, so ui_cancel goes in and the question stays up. Dismiss one with the key op and keycode Escape, or click its button."
|
|
13891
|
+
description: "action: the InputMap action name. A Viewport never hands an action to a shortcut, so the buttons whose Shortcut holds the action are sent it as their Shortcut too, and pressed as by the key bound to it: one button at most, the last in the tree that takes it, passing over one that is disabled, hidden or outside its shortcut context. The answer names those buttons under shortcuts and the one pressed under shortcut_pressed, null when none took it. An engine dialog is not answered this way: AcceptDialog reads the Escape key itself and never asks the InputMap, so ui_cancel goes in and the question stays up. Dismiss one with the key op and keycode Escape, or click its button."
|
|
13636
13892
|
},
|
|
13637
13893
|
pressed: {
|
|
13638
13894
|
type: "boolean",
|
|
@@ -13897,7 +14153,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13897
14153
|
{
|
|
13898
14154
|
name: "debug_control",
|
|
13899
14155
|
title: "Step the debugger",
|
|
13900
|
-
description: "Pauses, continues or steps the debugged game. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. A game the editor plays is moved through the editor's debug adapter, which cannot pause or step out: it answers a pause by reporting the game stopped and leaving it running, and implements no stepOut, so hold such a game with a breakpoint and step over or into from there. A game this server started itself has its keeper as its debugger, which does both: pause breaks into the game wherever it is, a script spinning in a loop included, which is how a game hung on its first frame is asked where it is.",
|
|
14156
|
+
description: "Pauses, continues or steps the debugged game. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. A game the editor plays is moved through the editor's debug adapter, which cannot pause or step out: it answers a pause by reporting the game stopped and leaving it running, and implements no stepOut, so hold such a game with a breakpoint and step over or into from there. A game this server started itself has its keeper as its debugger, which does both: pause breaks into the game wherever it is, a script spinning in a loop included, which is how a game hung on its first frame is asked where it is. For such a game a step that ends in gdharness's runtime addon, as one past a function's last line does when the addon's _process runs next, is stepped on to the game's own script as a pause is; and since the engine reports every stop the same way, a step that stops on a line holding a breakpoint says onBreakpoint under held, whether the step ended there or ran into it.",
|
|
13901
14157
|
parameters: {},
|
|
13902
14158
|
requires: [],
|
|
13903
14159
|
operations: {
|
|
@@ -13954,6 +14210,7 @@ var init_headless_operations = __esm(() => {
|
|
|
13954
14210
|
project_settings: dictionary({
|
|
13955
14211
|
get: "get_project_setting",
|
|
13956
14212
|
set: "set_project_setting",
|
|
14213
|
+
unset: "unset_project_setting",
|
|
13957
14214
|
add_autoload: "add_autoload",
|
|
13958
14215
|
remove_autoload: "remove_autoload",
|
|
13959
14216
|
set_main_scene: "set_main_scene",
|
|
@@ -14051,9 +14308,9 @@ function whatItCanChange(tool) {
|
|
|
14051
14308
|
if (destroying.length === 0) {
|
|
14052
14309
|
return "Adds or starts things, and overwrites and removes nothing itself.";
|
|
14053
14310
|
}
|
|
14054
|
-
return `Can overwrite, remove or end what is there, through ${
|
|
14311
|
+
return `Can overwrite, remove or end what is there, through ${listed2(destroying)}.`;
|
|
14055
14312
|
}
|
|
14056
|
-
function
|
|
14313
|
+
function listed2(items) {
|
|
14057
14314
|
const last = items.at(-1) ?? "";
|
|
14058
14315
|
return items.length === 1 ? last : `${items.slice(0, -1).join(", ")} and ${last}`;
|
|
14059
14316
|
}
|
|
@@ -14111,30 +14368,30 @@ var init_tool_reference = __esm(() => {
|
|
|
14111
14368
|
});
|
|
14112
14369
|
|
|
14113
14370
|
// src/desktop.ts
|
|
14114
|
-
import { createHash as
|
|
14371
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
14115
14372
|
import {
|
|
14116
14373
|
closeSync,
|
|
14117
|
-
existsSync as
|
|
14374
|
+
existsSync as existsSync16,
|
|
14118
14375
|
mkdtempSync as mkdtempSync2,
|
|
14119
14376
|
openSync,
|
|
14120
|
-
readFileSync as
|
|
14377
|
+
readFileSync as readFileSync13,
|
|
14121
14378
|
rmSync as rmSync7,
|
|
14122
|
-
statSync as
|
|
14379
|
+
statSync as statSync9,
|
|
14123
14380
|
writeFileSync as writeFileSync8,
|
|
14124
14381
|
writeSync
|
|
14125
14382
|
} from "node:fs";
|
|
14126
14383
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
14127
|
-
import { join as
|
|
14384
|
+
import { join as join18 } from "node:path";
|
|
14128
14385
|
import { setTimeout as delay } from "node:timers/promises";
|
|
14129
14386
|
function helperAssembly(directory = tmpdir4()) {
|
|
14130
|
-
return
|
|
14387
|
+
return join18(directory, `gdharness-desktop-${DIGEST}.dll`);
|
|
14131
14388
|
}
|
|
14132
14389
|
async function ensureHelperCompiled(directory = tmpdir4(), budgetMs = HELPER_COMPILE_MS, wait = true) {
|
|
14133
14390
|
const assembly = helperAssembly(directory);
|
|
14134
14391
|
const lock = `${assembly}.compiling`;
|
|
14135
14392
|
const until = Date.now() + budgetMs;
|
|
14136
14393
|
while (Date.now() < until) {
|
|
14137
|
-
if (
|
|
14394
|
+
if (existsSync16(assembly)) {
|
|
14138
14395
|
return null;
|
|
14139
14396
|
}
|
|
14140
14397
|
let mine = false;
|
|
@@ -14152,8 +14409,8 @@ async function ensureHelperCompiled(directory = tmpdir4(), budgetMs = HELPER_COM
|
|
|
14152
14409
|
}
|
|
14153
14410
|
}
|
|
14154
14411
|
try {
|
|
14155
|
-
const holder = Number(
|
|
14156
|
-
const old = Date.now() -
|
|
14412
|
+
const holder = Number(readFileSync13(lock, "utf8").trim());
|
|
14413
|
+
const old = Date.now() - statSync9(lock).mtimeMs > budgetMs;
|
|
14157
14414
|
if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
|
|
14158
14415
|
rmSync7(lock, { force: true });
|
|
14159
14416
|
continue;
|
|
@@ -14167,8 +14424,8 @@ async function ensureHelperCompiled(directory = tmpdir4(), budgetMs = HELPER_COM
|
|
|
14167
14424
|
return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
|
|
14168
14425
|
}
|
|
14169
14426
|
async function compileHelper(directory, assembly, budgetMs) {
|
|
14170
|
-
const scratch = mkdtempSync2(
|
|
14171
|
-
const source =
|
|
14427
|
+
const scratch = mkdtempSync2(join18(directory, "gdharness-desktop-source-"));
|
|
14428
|
+
const source = join18(scratch, "helper.cs");
|
|
14172
14429
|
writeFileSync8(source, SOURCE, "utf8");
|
|
14173
14430
|
const literal = (path) => `'${path.replaceAll("'", "''")}'`;
|
|
14174
14431
|
const script = [
|
|
@@ -14206,7 +14463,7 @@ async function compileHelper(directory, assembly, budgetMs) {
|
|
|
14206
14463
|
});
|
|
14207
14464
|
compiler.once("close", (code) => {
|
|
14208
14465
|
clearTimeout(timer);
|
|
14209
|
-
resolve(
|
|
14466
|
+
resolve(existsSync16(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
|
|
14210
14467
|
});
|
|
14211
14468
|
});
|
|
14212
14469
|
} finally {
|
|
@@ -14431,11 +14688,11 @@ public static class GdharnessDesktop {
|
|
|
14431
14688
|
var init_desktop = __esm(() => {
|
|
14432
14689
|
init_alive();
|
|
14433
14690
|
init_spawn();
|
|
14434
|
-
DIGEST =
|
|
14691
|
+
DIGEST = createHash5("sha256").update(SOURCE).digest("hex").slice(0, 16);
|
|
14435
14692
|
});
|
|
14436
14693
|
|
|
14437
14694
|
// src/suite-judge.ts
|
|
14438
|
-
import { readFileSync as
|
|
14695
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
14439
14696
|
import { stripVTControlCharacters } from "node:util";
|
|
14440
14697
|
function secondsOf(time) {
|
|
14441
14698
|
let ms = 0;
|
|
@@ -14540,9 +14797,16 @@ function readRun(printed) {
|
|
|
14540
14797
|
const unloadable = scriptErrorsPrinted(lines).map((error) => `${error.path}${error.line === null ? "" : `:${String(error.line)}`}: ${error.message}`);
|
|
14541
14798
|
return { lines, suites, errors, leaks, starved, executed, unloadable, stoppedIn };
|
|
14542
14799
|
}
|
|
14800
|
+
function withNestedErrors(printed, piped) {
|
|
14801
|
+
const nested = piped.flatMap((entry, at) => {
|
|
14802
|
+
const next = piped[at + 1];
|
|
14803
|
+
return entry.severity === "info" && entry.text.trim() === NESTED_ERROR && next?.severity === "error" ? [{ text: next.text, at: printed.lines.length, suite: null, during: null }] : [];
|
|
14804
|
+
});
|
|
14805
|
+
return nested.length === 0 ? printed : { ...printed, errors: [...printed.errors, ...nested] };
|
|
14806
|
+
}
|
|
14543
14807
|
function readExpected(file) {
|
|
14544
14808
|
try {
|
|
14545
|
-
const parsed = JSON.parse(
|
|
14809
|
+
const parsed = JSON.parse(readFileSync16(file, "utf8"));
|
|
14546
14810
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
14547
14811
|
} catch {
|
|
14548
14812
|
return {};
|
|
@@ -14710,7 +14974,7 @@ ${error.during}`);
|
|
|
14710
14974
|
const neverPrinted = [...owed.values()].flatMap((debt) => debt.texts.map((text) => ({ suite: debt.suite, during: debt.during, text })));
|
|
14711
14975
|
return { printed: unexpected, neverPrinted };
|
|
14712
14976
|
}
|
|
14713
|
-
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, EXECUTED, DRIVER_FALLBACK, LEAK, STARVED, WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
14977
|
+
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, EXECUTED, DRIVER_FALLBACK, LEAK, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
14714
14978
|
var init_suite_judge = __esm(() => {
|
|
14715
14979
|
init_junit();
|
|
14716
14980
|
SUITE_OPENED = /^Run Test Suite:\s*(res:\/\/.+?\.gd)\s*$/;
|
|
@@ -14776,7 +15040,7 @@ var init_whole_file = __esm(() => {
|
|
|
14776
15040
|
});
|
|
14777
15041
|
|
|
14778
15042
|
// src/suite-plan.ts
|
|
14779
|
-
import { existsSync as
|
|
15043
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync7, readFileSync as readFileSync17 } from "node:fs";
|
|
14780
15044
|
import { dirname as dirname10, posix as posix3 } from "node:path";
|
|
14781
15045
|
|
|
14782
15046
|
class ProjectScripts {
|
|
@@ -14865,7 +15129,7 @@ class ProjectScripts {
|
|
|
14865
15129
|
}
|
|
14866
15130
|
function readTimes(file) {
|
|
14867
15131
|
try {
|
|
14868
|
-
const parsed = JSON.parse(
|
|
15132
|
+
const parsed = JSON.parse(readFileSync17(file, "utf8"));
|
|
14869
15133
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
14870
15134
|
} catch {
|
|
14871
15135
|
return {};
|
|
@@ -14946,19 +15210,27 @@ function unitsOf(suites, times, splitPast = SPLIT_PAST_SECONDS) {
|
|
|
14946
15210
|
function dealt(units, declared, most) {
|
|
14947
15211
|
const windowed = units.filter((unit) => unit.windowed);
|
|
14948
15212
|
const headless = units.filter((unit) => !unit.windowed).sort((a, b) => b.cost - a.cost || a.suite.localeCompare(b.suite) || (a.cases[0] ?? "").localeCompare(b.cases[0] ?? ""));
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
const
|
|
14953
|
-
const
|
|
14954
|
-
const
|
|
15213
|
+
if (units.length === 0) {
|
|
15214
|
+
return [];
|
|
15215
|
+
}
|
|
15216
|
+
const together = windowed.reduce((sum, unit) => sum + unit.cost, 0);
|
|
15217
|
+
const total = units.reduce((sum, unit) => sum + unit.cost, 0);
|
|
15218
|
+
const slowest = Math.max(together, headless[0]?.cost ?? 0);
|
|
15219
|
+
const count = Math.max(1, Math.min(most, headless.length + (windowed.length > 0 ? 1 : 0), Math.ceil(total / Math.max(slowest, ENGINE_START_SECONDS))));
|
|
15220
|
+
const bins = Array.from({ length: count }, (_, index) => index === 0 ? [...windowed] : []);
|
|
15221
|
+
const loads = Array.from({ length: count }, (_, index) => index === 0 ? together : 0);
|
|
14955
15222
|
for (const unit of headless) {
|
|
14956
15223
|
const lightest = loads.indexOf(Math.min(...loads));
|
|
14957
15224
|
bins[lightest]?.push(unit);
|
|
14958
15225
|
loads[lightest] = (loads[lightest] ?? 0) + unit.cost;
|
|
14959
15226
|
}
|
|
14960
|
-
|
|
14961
|
-
|
|
15227
|
+
return bins.map((bin, index) => planOf(bin, declared, index === 0 && windowed.length > 0));
|
|
15228
|
+
}
|
|
15229
|
+
function windowedSaid(plans) {
|
|
15230
|
+
if (!plans.some((plan) => plan.windowed)) {
|
|
15231
|
+
return "";
|
|
15232
|
+
}
|
|
15233
|
+
return plans.length === 1 ? ", with a window" : ", one of them windowed";
|
|
14962
15234
|
}
|
|
14963
15235
|
function inOneEngine(units, declared) {
|
|
14964
15236
|
return planOf(units, declared, units.some((unit) => unit.windowed));
|
|
@@ -14978,10 +15250,10 @@ function planOf(bin, declared, windowed) {
|
|
|
14978
15250
|
}
|
|
14979
15251
|
function viewportOf(projectPath) {
|
|
14980
15252
|
const file = `${projectPath}/project.godot`;
|
|
14981
|
-
if (!
|
|
15253
|
+
if (!existsSync18(file)) {
|
|
14982
15254
|
return null;
|
|
14983
15255
|
}
|
|
14984
|
-
const text =
|
|
15256
|
+
const text = readFileSync17(file, "utf8");
|
|
14985
15257
|
const width = /^window\/size\/viewport_width\s*=\s*(\d+)/m.exec(text)?.[1];
|
|
14986
15258
|
const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
|
|
14987
15259
|
return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
|
|
@@ -36597,13 +36869,13 @@ var init_stdio = __esm(() => {
|
|
|
36597
36869
|
});
|
|
36598
36870
|
|
|
36599
36871
|
// src/autoload-failure.ts
|
|
36600
|
-
import { existsSync as
|
|
36601
|
-
import { join as
|
|
36872
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "node:fs";
|
|
36873
|
+
import { join as join23 } from "node:path";
|
|
36602
36874
|
function scriptsAutoloaded(projectPath) {
|
|
36603
36875
|
const scripts = new Map;
|
|
36604
36876
|
let settings;
|
|
36605
36877
|
try {
|
|
36606
|
-
settings = parseProjectGodot(
|
|
36878
|
+
settings = parseProjectGodot(readFileSync20(join23(projectPath, "project.godot"), "utf8"));
|
|
36607
36879
|
} catch {
|
|
36608
36880
|
return scripts;
|
|
36609
36881
|
}
|
|
@@ -36612,12 +36884,12 @@ function scriptsAutoloaded(projectPath) {
|
|
|
36612
36884
|
scripts.set(`res://${path}`, name);
|
|
36613
36885
|
continue;
|
|
36614
36886
|
}
|
|
36615
|
-
const scene =
|
|
36616
|
-
if (!
|
|
36887
|
+
const scene = join23(projectPath, path);
|
|
36888
|
+
if (!existsSync20(scene)) {
|
|
36617
36889
|
continue;
|
|
36618
36890
|
}
|
|
36619
36891
|
try {
|
|
36620
|
-
for (const named of
|
|
36892
|
+
for (const named of readFileSync20(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
|
|
36621
36893
|
scripts.set(named[1] ?? "", name);
|
|
36622
36894
|
}
|
|
36623
36895
|
} catch {}
|
|
@@ -36636,10 +36908,10 @@ var init_autoload_failure = __esm(() => {
|
|
|
36636
36908
|
});
|
|
36637
36909
|
|
|
36638
36910
|
// src/runtime-client.ts
|
|
36639
|
-
import { existsSync as
|
|
36911
|
+
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync21, statSync as statSync13, unlinkSync as unlinkSync2 } from "node:fs";
|
|
36640
36912
|
import { createConnection } from "node:net";
|
|
36641
36913
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
36642
|
-
import { dirname as dirname12, join as
|
|
36914
|
+
import { dirname as dirname12, join as join24, resolve as resolve5 } from "node:path";
|
|
36643
36915
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
36644
36916
|
function runtimeDirectory(variables = process.env) {
|
|
36645
36917
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -36647,20 +36919,20 @@ function runtimeDirectory(variables = process.env) {
|
|
|
36647
36919
|
return explicit;
|
|
36648
36920
|
}
|
|
36649
36921
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
36650
|
-
return
|
|
36922
|
+
return join24(perUser ?? tmpdir5(), "gdharness");
|
|
36651
36923
|
}
|
|
36652
36924
|
function runtimeDirectories(variables = process.env) {
|
|
36653
36925
|
const candidates = [runtimeDirectory(variables)];
|
|
36654
36926
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
36655
36927
|
for (const base of fallbacks) {
|
|
36656
|
-
candidates.push(
|
|
36928
|
+
candidates.push(join24(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
36657
36929
|
}
|
|
36658
36930
|
return [...new Set(candidates.map((path) => resolve5(path)))];
|
|
36659
36931
|
}
|
|
36660
36932
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
36661
36933
|
for (const directory of directories) {
|
|
36662
|
-
const path =
|
|
36663
|
-
if (
|
|
36934
|
+
const path = join24(directory, `runtime-${pid}.log`);
|
|
36935
|
+
if (existsSync21(path)) {
|
|
36664
36936
|
return path;
|
|
36665
36937
|
}
|
|
36666
36938
|
}
|
|
@@ -36691,7 +36963,7 @@ function reportAlreadyInTranscript(transcript, report) {
|
|
|
36691
36963
|
function parseAnnouncement(file, pid) {
|
|
36692
36964
|
let fields;
|
|
36693
36965
|
try {
|
|
36694
|
-
fields = asParams(JSON.parse(
|
|
36966
|
+
fields = asParams(JSON.parse(readFileSync21(file, "utf8")));
|
|
36695
36967
|
} catch {
|
|
36696
36968
|
return { kind: "rubbish" };
|
|
36697
36969
|
}
|
|
@@ -36708,7 +36980,7 @@ function parseAnnouncement(file, pid) {
|
|
|
36708
36980
|
};
|
|
36709
36981
|
let announcedAt;
|
|
36710
36982
|
try {
|
|
36711
|
-
announcedAt =
|
|
36983
|
+
announcedAt = statSync13(file).mtimeMs;
|
|
36712
36984
|
} catch {}
|
|
36713
36985
|
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
36714
36986
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
@@ -36761,7 +37033,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
|
|
|
36761
37033
|
function wentAway(file, pid) {
|
|
36762
37034
|
let project = "";
|
|
36763
37035
|
try {
|
|
36764
|
-
const fields = asParams(JSON.parse(
|
|
37036
|
+
const fields = asParams(JSON.parse(readFileSync21(file, "utf8")));
|
|
36765
37037
|
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
36766
37038
|
} catch {
|
|
36767
37039
|
return;
|
|
@@ -36804,29 +37076,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
36804
37076
|
}).map((one) => one.file));
|
|
36805
37077
|
}
|
|
36806
37078
|
function announcedIn(directory) {
|
|
36807
|
-
if (!
|
|
37079
|
+
if (!existsSync21(directory)) {
|
|
36808
37080
|
return [];
|
|
36809
37081
|
}
|
|
36810
37082
|
const found = [];
|
|
36811
37083
|
const candidates = [];
|
|
36812
37084
|
const entries = [];
|
|
36813
|
-
for (const entry of
|
|
37085
|
+
for (const entry of readdirSync13(directory)) {
|
|
36814
37086
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
36815
37087
|
if (report) {
|
|
36816
|
-
sweepErrorReport(
|
|
37088
|
+
sweepErrorReport(join24(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
36817
37089
|
continue;
|
|
36818
37090
|
}
|
|
36819
37091
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
36820
37092
|
if (!match) {
|
|
36821
37093
|
continue;
|
|
36822
37094
|
}
|
|
36823
|
-
const file =
|
|
37095
|
+
const file = join24(directory, entry);
|
|
36824
37096
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
36825
37097
|
const running = alive(pid);
|
|
36826
37098
|
entries.push({ file, pid, running });
|
|
36827
37099
|
if (running) {
|
|
36828
37100
|
try {
|
|
36829
|
-
candidates.push({ file, pid, writtenAt:
|
|
37101
|
+
candidates.push({ file, pid, writtenAt: statSync13(file).mtimeMs });
|
|
36830
37102
|
} catch {}
|
|
36831
37103
|
}
|
|
36832
37104
|
}
|
|
@@ -36848,7 +37120,7 @@ function announcedIn(directory) {
|
|
|
36848
37120
|
found.push(announced);
|
|
36849
37121
|
}
|
|
36850
37122
|
const held = new Set(entries.map((one) => one.file));
|
|
36851
|
-
const here =
|
|
37123
|
+
const here = join24(directory, ".");
|
|
36852
37124
|
for (const file of verdicts.keys()) {
|
|
36853
37125
|
if (dirname12(file) === here && !held.has(file)) {
|
|
36854
37126
|
verdicts.delete(file);
|
|
@@ -36857,7 +37129,7 @@ function announcedIn(directory) {
|
|
|
36857
37129
|
return found;
|
|
36858
37130
|
}
|
|
36859
37131
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
36860
|
-
const files = directories.map((directory) =>
|
|
37132
|
+
const files = directories.map((directory) => join24(directory, `runtime-${pid}.json`)).filter((file) => existsSync21(file));
|
|
36861
37133
|
const first = files[0];
|
|
36862
37134
|
if (first === undefined) {
|
|
36863
37135
|
return;
|
|
@@ -36883,7 +37155,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
36883
37155
|
return;
|
|
36884
37156
|
}
|
|
36885
37157
|
try {
|
|
36886
|
-
if (now -
|
|
37158
|
+
if (now - statSync13(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
36887
37159
|
unlinkSync2(path);
|
|
36888
37160
|
}
|
|
36889
37161
|
} catch {}
|
|
@@ -37276,14 +37548,14 @@ var init_runtime_client = __esm(() => {
|
|
|
37276
37548
|
});
|
|
37277
37549
|
|
|
37278
37550
|
// src/boot-note.ts
|
|
37279
|
-
import { mkdirSync as mkdirSync9, readFileSync as
|
|
37280
|
-
import { dirname as dirname13, join as
|
|
37551
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "node:fs";
|
|
37552
|
+
import { dirname as dirname13, join as join25 } from "node:path";
|
|
37281
37553
|
function bootNotePath(projectPath) {
|
|
37282
|
-
return
|
|
37554
|
+
return join25(projectPath, ".godot", "gdharness-boot.json");
|
|
37283
37555
|
}
|
|
37284
37556
|
function readScenes(projectPath) {
|
|
37285
37557
|
try {
|
|
37286
|
-
return asParams(asParams(JSON.parse(
|
|
37558
|
+
return asParams(asParams(JSON.parse(readFileSync22(bootNotePath(projectPath), "utf8")))["scenes"]);
|
|
37287
37559
|
} catch {
|
|
37288
37560
|
return {};
|
|
37289
37561
|
}
|
|
@@ -37325,10 +37597,10 @@ var init_boot_note = __esm(() => {
|
|
|
37325
37597
|
});
|
|
37326
37598
|
|
|
37327
37599
|
// src/breakpoint-note.ts
|
|
37328
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
37329
|
-
import { dirname as dirname14, isAbsolute as isAbsolute5, join as
|
|
37600
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync23, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
37601
|
+
import { dirname as dirname14, isAbsolute as isAbsolute5, join as join26, relative as relative4 } from "node:path";
|
|
37330
37602
|
function breakpointNotePath(projectPath) {
|
|
37331
|
-
return
|
|
37603
|
+
return join26(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
37332
37604
|
}
|
|
37333
37605
|
function writeBreakpointNote(projectPath, held) {
|
|
37334
37606
|
const path = breakpointNotePath(projectPath);
|
|
@@ -37354,7 +37626,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
37354
37626
|
}
|
|
37355
37627
|
function readBreakpointNote(projectPath) {
|
|
37356
37628
|
try {
|
|
37357
|
-
const parsed = JSON.parse(
|
|
37629
|
+
const parsed = JSON.parse(readFileSync23(breakpointNotePath(projectPath), "utf8"));
|
|
37358
37630
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
|
|
37359
37631
|
return [];
|
|
37360
37632
|
}
|
|
@@ -37369,7 +37641,7 @@ function readBreakpointNote(projectPath) {
|
|
|
37369
37641
|
}
|
|
37370
37642
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
37371
37643
|
if (lines.length > 0) {
|
|
37372
|
-
held.push({ scriptPath: realAsFarAsItExists(
|
|
37644
|
+
held.push({ scriptPath: realAsFarAsItExists(join26(projectPath, script)), lines });
|
|
37373
37645
|
}
|
|
37374
37646
|
}
|
|
37375
37647
|
return held;
|
|
@@ -37387,14 +37659,14 @@ var init_breakpoint_note = __esm(() => {
|
|
|
37387
37659
|
});
|
|
37388
37660
|
|
|
37389
37661
|
// src/class-note.ts
|
|
37390
|
-
import { mkdirSync as mkdirSync11, readFileSync as
|
|
37391
|
-
import { dirname as dirname15, join as
|
|
37662
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync24 } from "node:fs";
|
|
37663
|
+
import { dirname as dirname15, join as join27 } from "node:path";
|
|
37392
37664
|
function classNotePath(projectPath) {
|
|
37393
|
-
return
|
|
37665
|
+
return join27(projectPath, ".godot", "gdharness-classes.json");
|
|
37394
37666
|
}
|
|
37395
37667
|
function readClassNote(projectPath) {
|
|
37396
37668
|
try {
|
|
37397
|
-
const fields = asParams(JSON.parse(
|
|
37669
|
+
const fields = asParams(JSON.parse(readFileSync24(classNotePath(projectPath), "utf8")));
|
|
37398
37670
|
const rebuiltWith = readArray(fields, "rebuiltWith");
|
|
37399
37671
|
if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
|
|
37400
37672
|
return null;
|
|
@@ -37436,16 +37708,16 @@ var init_class_note = __esm(() => {
|
|
|
37436
37708
|
});
|
|
37437
37709
|
|
|
37438
37710
|
// src/config-pin.ts
|
|
37439
|
-
import { existsSync as
|
|
37440
|
-
import { join as
|
|
37711
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25 } from "node:fs";
|
|
37712
|
+
import { join as join28 } from "node:path";
|
|
37441
37713
|
function versionsNamedInConfig(projectPath) {
|
|
37442
|
-
const config =
|
|
37443
|
-
if (!
|
|
37714
|
+
const config = join28(projectPath, ".mcp.json");
|
|
37715
|
+
if (!existsSync22(config)) {
|
|
37444
37716
|
return [];
|
|
37445
37717
|
}
|
|
37446
37718
|
let text;
|
|
37447
37719
|
try {
|
|
37448
|
-
text =
|
|
37720
|
+
text = readFileSync25(config, "utf8");
|
|
37449
37721
|
JSON.parse(text);
|
|
37450
37722
|
} catch {
|
|
37451
37723
|
return [];
|
|
@@ -37466,10 +37738,10 @@ var init_config_pin = __esm(() => {
|
|
|
37466
37738
|
});
|
|
37467
37739
|
|
|
37468
37740
|
// src/cut-test-run.ts
|
|
37469
|
-
import { existsSync as
|
|
37470
|
-
import { dirname as dirname16, join as
|
|
37741
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync12, writeFileSync as writeFileSync13 } from "node:fs";
|
|
37742
|
+
import { dirname as dirname16, join as join29 } from "node:path";
|
|
37471
37743
|
function keptAt(projectPath) {
|
|
37472
|
-
return
|
|
37744
|
+
return join29(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
37473
37745
|
}
|
|
37474
37746
|
function keepCutTestRun(projectPath, run) {
|
|
37475
37747
|
const path = keptAt(projectPath);
|
|
@@ -37481,11 +37753,11 @@ function keepCutTestRun(projectPath, run) {
|
|
|
37481
37753
|
}
|
|
37482
37754
|
function takeCutTestRun(projectPath) {
|
|
37483
37755
|
const path = keptAt(projectPath);
|
|
37484
|
-
if (!
|
|
37756
|
+
if (!existsSync23(path)) {
|
|
37485
37757
|
return null;
|
|
37486
37758
|
}
|
|
37487
37759
|
try {
|
|
37488
|
-
const run = JSON.parse(
|
|
37760
|
+
const run = JSON.parse(readFileSync26(path, "utf8"));
|
|
37489
37761
|
rmSync12(path, { force: true });
|
|
37490
37762
|
return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
|
|
37491
37763
|
} catch {
|
|
@@ -38298,25 +38570,25 @@ var init_dap_client = __esm(() => {
|
|
|
38298
38570
|
// src/editor-log.ts
|
|
38299
38571
|
import {
|
|
38300
38572
|
closeSync as closeSync4,
|
|
38301
|
-
existsSync as
|
|
38573
|
+
existsSync as existsSync24,
|
|
38302
38574
|
mkdirSync as mkdirSync13,
|
|
38303
38575
|
openSync as openSync4,
|
|
38304
|
-
readFileSync as
|
|
38576
|
+
readFileSync as readFileSync27,
|
|
38305
38577
|
readSync as readSync2,
|
|
38306
38578
|
rmSync as rmSync13,
|
|
38307
|
-
statSync as
|
|
38579
|
+
statSync as statSync14,
|
|
38308
38580
|
writeFileSync as writeFileSync14
|
|
38309
38581
|
} from "node:fs";
|
|
38310
|
-
import { dirname as dirname17, join as
|
|
38582
|
+
import { dirname as dirname17, join as join30 } from "node:path";
|
|
38311
38583
|
function editorLogPath(projectPath) {
|
|
38312
|
-
return
|
|
38584
|
+
return join30(projectPath, ".godot", "gdharness-editor.log");
|
|
38313
38585
|
}
|
|
38314
38586
|
function editorLogNotePath(projectPath) {
|
|
38315
|
-
return
|
|
38587
|
+
return join30(projectPath, ".godot", "gdharness-editor.json");
|
|
38316
38588
|
}
|
|
38317
38589
|
function readEditorLogNote(projectPath) {
|
|
38318
38590
|
try {
|
|
38319
|
-
const fields = asParams(JSON.parse(
|
|
38591
|
+
const fields = asParams(JSON.parse(readFileSync27(editorLogNotePath(projectPath), "utf8")));
|
|
38320
38592
|
const pid = readNumber(fields, "pid");
|
|
38321
38593
|
const startedAt = readString(fields, "startedAt");
|
|
38322
38594
|
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
@@ -38357,7 +38629,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
38357
38629
|
return { kind: "another editor", notedPid: noted?.pid ?? null };
|
|
38358
38630
|
}
|
|
38359
38631
|
const path = editorLogPath(projectPath);
|
|
38360
|
-
if (!
|
|
38632
|
+
if (!existsSync24(path)) {
|
|
38361
38633
|
return { kind: "not written" };
|
|
38362
38634
|
}
|
|
38363
38635
|
const log = new GameLog;
|
|
@@ -38413,9 +38685,9 @@ function foldProgress(text) {
|
|
|
38413
38685
|
`);
|
|
38414
38686
|
}
|
|
38415
38687
|
function theFirstOf(path) {
|
|
38416
|
-
const whole =
|
|
38688
|
+
const whole = statSync14(path).size;
|
|
38417
38689
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
38418
|
-
return { text:
|
|
38690
|
+
return { text: readFileSync27(path, "utf8") };
|
|
38419
38691
|
}
|
|
38420
38692
|
const handle = openSync4(path, "r");
|
|
38421
38693
|
try {
|
|
@@ -38576,7 +38848,7 @@ var init_editor_log = __esm(() => {
|
|
|
38576
38848
|
});
|
|
38577
38849
|
|
|
38578
38850
|
// src/rename.ts
|
|
38579
|
-
import { existsSync as
|
|
38851
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync28, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "node:fs";
|
|
38580
38852
|
import { dirname as dirname18, posix as posix4 } from "node:path";
|
|
38581
38853
|
|
|
38582
38854
|
class Script {
|
|
@@ -39281,11 +39553,11 @@ function rewritesOf(plan, texts, move) {
|
|
|
39281
39553
|
}
|
|
39282
39554
|
function applyRewrites(rewrites) {
|
|
39283
39555
|
for (const rewrite of rewrites) {
|
|
39284
|
-
const now =
|
|
39556
|
+
const now = readFileSync28(rewrite.absolute, "utf8");
|
|
39285
39557
|
if (now !== rewrite.before) {
|
|
39286
39558
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
39287
39559
|
}
|
|
39288
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
39560
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync25(rewrite.writeTo)) {
|
|
39289
39561
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
39290
39562
|
}
|
|
39291
39563
|
}
|
|
@@ -39299,7 +39571,7 @@ function applyRewrites(rewrites) {
|
|
|
39299
39571
|
let movedUid = false;
|
|
39300
39572
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
39301
39573
|
unlinkSync3(rewrite.absolute);
|
|
39302
|
-
if (
|
|
39574
|
+
if (existsSync25(`${rewrite.absolute}.uid`)) {
|
|
39303
39575
|
renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
39304
39576
|
movedUid = true;
|
|
39305
39577
|
}
|
|
@@ -39345,10 +39617,10 @@ var init_rename = __esm(() => {
|
|
|
39345
39617
|
});
|
|
39346
39618
|
|
|
39347
39619
|
// src/lsp_client.ts
|
|
39348
|
-
import { existsSync as
|
|
39620
|
+
import { existsSync as existsSync26, realpathSync as realpathSync3 } from "node:fs";
|
|
39349
39621
|
import { readFile, realpath } from "node:fs/promises";
|
|
39350
39622
|
import { createConnection as createConnection3 } from "node:net";
|
|
39351
|
-
import { dirname as dirname19, join as
|
|
39623
|
+
import { dirname as dirname19, join as join31, resolve as resolve6 } from "node:path";
|
|
39352
39624
|
import { setTimeout as delay6 } from "node:timers/promises";
|
|
39353
39625
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
39354
39626
|
function initializeTimeoutMs() {
|
|
@@ -40026,7 +40298,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
40026
40298
|
} catch {
|
|
40027
40299
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
40028
40300
|
}
|
|
40029
|
-
if (!
|
|
40301
|
+
if (!existsSync26(join31(projectPath, "project.godot"))) {
|
|
40030
40302
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
40031
40303
|
}
|
|
40032
40304
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -40186,8 +40458,8 @@ var init_lsp_client = __esm(() => {
|
|
|
40186
40458
|
});
|
|
40187
40459
|
|
|
40188
40460
|
// src/rename-symbol.ts
|
|
40189
|
-
import { existsSync as
|
|
40190
|
-
import { basename as basename8, dirname as dirname20, join as
|
|
40461
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync15, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync14, writeFileSync as writeFileSync16 } from "node:fs";
|
|
40462
|
+
import { basename as basename8, dirname as dirname20, join as join32, relative as relative5, resolve as resolve7 } from "node:path";
|
|
40191
40463
|
function resourcePathOf(projectPath, absolute) {
|
|
40192
40464
|
const inside = relative5(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
40193
40465
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
@@ -40200,7 +40472,7 @@ function onDiskSpelling(path) {
|
|
|
40200
40472
|
let at = resolve7(path);
|
|
40201
40473
|
for (;; ) {
|
|
40202
40474
|
try {
|
|
40203
|
-
return
|
|
40475
|
+
return join32(realpathSync4.native(at), ...missing.reverse());
|
|
40204
40476
|
} catch {
|
|
40205
40477
|
const parent = dirname20(at);
|
|
40206
40478
|
if (parent === at) {
|
|
@@ -40254,7 +40526,7 @@ async function renameSymbol(request, services) {
|
|
|
40254
40526
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
40255
40527
|
};
|
|
40256
40528
|
}
|
|
40257
|
-
if (
|
|
40529
|
+
if (existsSync27(request.newScriptPath)) {
|
|
40258
40530
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
40259
40531
|
}
|
|
40260
40532
|
}
|
|
@@ -40430,7 +40702,7 @@ async function renameSymbol(request, services) {
|
|
|
40430
40702
|
scripts,
|
|
40431
40703
|
moves: new Map([[declaringPath, moveTo]]),
|
|
40432
40704
|
named: [declaringPath],
|
|
40433
|
-
exists: (path) =>
|
|
40705
|
+
exists: (path) => existsSync27(`${projectPath}/${path.slice("res://".length)}`)
|
|
40434
40706
|
}, plan);
|
|
40435
40707
|
}
|
|
40436
40708
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
|
|
@@ -40629,15 +40901,15 @@ ${mention.kind}`;
|
|
|
40629
40901
|
};
|
|
40630
40902
|
}
|
|
40631
40903
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
40632
|
-
const directory =
|
|
40904
|
+
const directory = join32(projectPath, ".godot", "gdharness-renames");
|
|
40633
40905
|
mkdirSync15(directory, { recursive: true });
|
|
40634
40906
|
detailWrites += 1;
|
|
40635
|
-
const path =
|
|
40907
|
+
const path = join32(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
40636
40908
|
writeFileSync16(path, `${JSON.stringify(detail, null, 2)}
|
|
40637
40909
|
`, "utf8");
|
|
40638
|
-
const kept =
|
|
40910
|
+
const kept = readdirSync14(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
40639
40911
|
for (const old of kept.slice(10)) {
|
|
40640
|
-
rmSync14(
|
|
40912
|
+
rmSync14(join32(directory, old), { force: true });
|
|
40641
40913
|
}
|
|
40642
40914
|
return path;
|
|
40643
40915
|
}
|
|
@@ -40678,25 +40950,25 @@ var init_rename_symbol = __esm(() => {
|
|
|
40678
40950
|
|
|
40679
40951
|
// src/file-move.ts
|
|
40680
40952
|
import {
|
|
40681
|
-
existsSync as
|
|
40953
|
+
existsSync as existsSync28,
|
|
40682
40954
|
mkdirSync as mkdirSync16,
|
|
40683
|
-
readdirSync as
|
|
40684
|
-
readFileSync as
|
|
40955
|
+
readdirSync as readdirSync15,
|
|
40956
|
+
readFileSync as readFileSync29,
|
|
40685
40957
|
renameSync as renameSync4,
|
|
40686
|
-
statSync as
|
|
40958
|
+
statSync as statSync15,
|
|
40687
40959
|
writeFileSync as writeFileSync17
|
|
40688
40960
|
} from "node:fs";
|
|
40689
|
-
import { basename as basename9, dirname as dirname21, extname as extname3, join as
|
|
40961
|
+
import { basename as basename9, dirname as dirname21, extname as extname3, join as join33, relative as relative6 } from "node:path";
|
|
40690
40962
|
function filesUnder(directory) {
|
|
40691
|
-
return
|
|
40692
|
-
const path =
|
|
40963
|
+
return readdirSync15(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
40964
|
+
const path = join33(directory, entry.name);
|
|
40693
40965
|
return entry.isDirectory() ? filesUnder(path) : [path];
|
|
40694
40966
|
});
|
|
40695
40967
|
}
|
|
40696
40968
|
function sameFile(one, other) {
|
|
40697
40969
|
try {
|
|
40698
|
-
const a =
|
|
40699
|
-
const b =
|
|
40970
|
+
const a = statSync15(one);
|
|
40971
|
+
const b = statSync15(other);
|
|
40700
40972
|
return a.ino === b.ino && a.dev === b.dev;
|
|
40701
40973
|
} catch {
|
|
40702
40974
|
return false;
|
|
@@ -40710,8 +40982,8 @@ function unseenByTheEngine(projectPath, path) {
|
|
|
40710
40982
|
return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
|
|
40711
40983
|
}
|
|
40712
40984
|
if (at < steps.length - 1) {
|
|
40713
|
-
directory =
|
|
40714
|
-
if (
|
|
40985
|
+
directory = join33(directory, step);
|
|
40986
|
+
if (existsSync28(join33(directory, ".gdignore"))) {
|
|
40715
40987
|
return `${path} is under a directory holding a .gdignore, which the engine does not load`;
|
|
40716
40988
|
}
|
|
40717
40989
|
}
|
|
@@ -40723,21 +40995,21 @@ function installedAddonOf(projectPath, path) {
|
|
|
40723
40995
|
if (steps[0] !== "addons" || steps[1] === undefined) {
|
|
40724
40996
|
return null;
|
|
40725
40997
|
}
|
|
40726
|
-
return
|
|
40998
|
+
return existsSync28(join33(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
|
|
40727
40999
|
}
|
|
40728
41000
|
function binaryResources(projectPath) {
|
|
40729
41001
|
const found = [];
|
|
40730
41002
|
const visit = (directory, prefix) => {
|
|
40731
|
-
if (
|
|
41003
|
+
if (existsSync28(join33(directory, ".gdignore"))) {
|
|
40732
41004
|
return;
|
|
40733
41005
|
}
|
|
40734
|
-
for (const entry of
|
|
41006
|
+
for (const entry of readdirSync15(directory, { withFileTypes: true })) {
|
|
40735
41007
|
if (entry.name.startsWith(".")) {
|
|
40736
41008
|
continue;
|
|
40737
41009
|
}
|
|
40738
41010
|
const path = `${prefix}${entry.name}`;
|
|
40739
41011
|
if (entry.isDirectory()) {
|
|
40740
|
-
visit(
|
|
41012
|
+
visit(join33(directory, entry.name), `${path}/`);
|
|
40741
41013
|
} else if (/\.(scn|res)$/i.test(entry.name)) {
|
|
40742
41014
|
found.push(path);
|
|
40743
41015
|
}
|
|
@@ -40747,7 +41019,7 @@ function binaryResources(projectPath) {
|
|
|
40747
41019
|
return found;
|
|
40748
41020
|
}
|
|
40749
41021
|
function targetPathOf(projectPath, absolute) {
|
|
40750
|
-
const inside = relative6(onDiskSpelling(projectPath),
|
|
41022
|
+
const inside = relative6(onDiskSpelling(projectPath), join33(onDiskSpelling(dirname21(absolute)), basename9(absolute)));
|
|
40751
41023
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
40752
41024
|
return null;
|
|
40753
41025
|
}
|
|
@@ -40765,13 +41037,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40765
41037
|
if (to === null) {
|
|
40766
41038
|
return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
|
|
40767
41039
|
}
|
|
40768
|
-
if (!
|
|
41040
|
+
if (!existsSync28(absoluteFrom)) {
|
|
40769
41041
|
return { ok: false, reason: `There is nothing at ${from} to move.` };
|
|
40770
41042
|
}
|
|
40771
41043
|
if (from === to) {
|
|
40772
41044
|
return { ok: false, reason: `${from} is already there; nothing to move.` };
|
|
40773
41045
|
}
|
|
40774
|
-
const isDirectory =
|
|
41046
|
+
const isDirectory = statSync15(absoluteFrom).isDirectory();
|
|
40775
41047
|
if (from === "res://project.godot") {
|
|
40776
41048
|
return {
|
|
40777
41049
|
ok: false,
|
|
@@ -40794,7 +41066,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40794
41066
|
if (isDirectory && to.startsWith(`${from}/`)) {
|
|
40795
41067
|
return { ok: false, reason: `${from} cannot be moved into itself.` };
|
|
40796
41068
|
}
|
|
40797
|
-
if (
|
|
41069
|
+
if (existsSync28(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
|
|
40798
41070
|
return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
|
|
40799
41071
|
}
|
|
40800
41072
|
const unseen = unseenByTheEngine(projectPath, to);
|
|
@@ -40802,7 +41074,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40802
41074
|
return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
|
|
40803
41075
|
}
|
|
40804
41076
|
if (!isDirectory) {
|
|
40805
|
-
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null &&
|
|
41077
|
+
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync28(join33(projectPath, path.slice("res://".length))));
|
|
40806
41078
|
if (owner !== undefined && owner !== null) {
|
|
40807
41079
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
40808
41080
|
}
|
|
@@ -40824,7 +41096,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40824
41096
|
} else {
|
|
40825
41097
|
moves.set(from, to);
|
|
40826
41098
|
for (const sidecar of SIDECARS) {
|
|
40827
|
-
if (
|
|
41099
|
+
if (existsSync28(`${absoluteFrom}${sidecar}`)) {
|
|
40828
41100
|
moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
|
|
40829
41101
|
}
|
|
40830
41102
|
}
|
|
@@ -40836,12 +41108,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40836
41108
|
scripts,
|
|
40837
41109
|
moves,
|
|
40838
41110
|
named: [from],
|
|
40839
|
-
exists: (path) =>
|
|
41111
|
+
exists: (path) => existsSync28(join33(projectPath, path.slice("res://".length)))
|
|
40840
41112
|
}, { edits: [], mentions: [] });
|
|
40841
41113
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
|
|
40842
41114
|
const binaries = [];
|
|
40843
41115
|
for (const path of binaryResources(projectPath)) {
|
|
40844
|
-
const bytes =
|
|
41116
|
+
const bytes = readFileSync29(join33(projectPath, path.slice("res://".length)));
|
|
40845
41117
|
const names = [...moves.keys()].find((moving) => bytes.includes(moving));
|
|
40846
41118
|
if (names !== undefined) {
|
|
40847
41119
|
binaries.push({
|
|
@@ -40860,7 +41132,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40860
41132
|
}));
|
|
40861
41133
|
const left = leftAloneReport(mentions);
|
|
40862
41134
|
const rewrites = rewritesOf(plan, texts, null);
|
|
40863
|
-
const absoluteOf = (path) =>
|
|
41135
|
+
const absoluteOf = (path) => join33(projectPath, path.slice("res://".length));
|
|
40864
41136
|
const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
|
|
40865
41137
|
const moved = [...moves].map(([source, target]) => {
|
|
40866
41138
|
const rewrite = rewritten.get(source);
|
|
@@ -40902,18 +41174,18 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40902
41174
|
}
|
|
40903
41175
|
function applyFileMove(outcome) {
|
|
40904
41176
|
for (const rewrite of outcome.inPlace) {
|
|
40905
|
-
if (
|
|
41177
|
+
if (readFileSync29(rewrite.absolute, "utf8") !== rewrite.before) {
|
|
40906
41178
|
throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
|
|
40907
41179
|
}
|
|
40908
41180
|
}
|
|
40909
41181
|
for (const file of outcome.moved) {
|
|
40910
|
-
if (!
|
|
41182
|
+
if (!existsSync28(file.absoluteFrom)) {
|
|
40911
41183
|
throw new Error(`${file.from} is gone from disk; nothing was written`);
|
|
40912
41184
|
}
|
|
40913
|
-
if (file.text !== null &&
|
|
41185
|
+
if (file.text !== null && readFileSync29(file.absoluteFrom, "utf8") !== file.text.before) {
|
|
40914
41186
|
throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
|
|
40915
41187
|
}
|
|
40916
|
-
if (
|
|
41188
|
+
if (existsSync28(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
|
|
40917
41189
|
throw new Error(`${file.to} already exists; nothing was written`);
|
|
40918
41190
|
}
|
|
40919
41191
|
}
|
|
@@ -44580,168 +44852,6 @@ var init_godot_bridge = __esm(() => {
|
|
|
44580
44852
|
};
|
|
44581
44853
|
});
|
|
44582
44854
|
|
|
44583
|
-
// src/import-freshness.ts
|
|
44584
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
44585
|
-
import { existsSync as existsSync28, readdirSync as readdirSync15, readFileSync as readFileSync29, statSync as statSync15 } from "node:fs";
|
|
44586
|
-
import { join as join33 } from "node:path";
|
|
44587
|
-
function md5Of(file) {
|
|
44588
|
-
let stats;
|
|
44589
|
-
try {
|
|
44590
|
-
stats = statSync15(file);
|
|
44591
|
-
} catch {
|
|
44592
|
-
return null;
|
|
44593
|
-
}
|
|
44594
|
-
const known = hashes.get(file);
|
|
44595
|
-
if (known !== undefined && known.size === stats.size && known.mtimeMs === stats.mtimeMs) {
|
|
44596
|
-
return known.md5;
|
|
44597
|
-
}
|
|
44598
|
-
let md5;
|
|
44599
|
-
try {
|
|
44600
|
-
md5 = createHash6("md5").update(readFileSync29(file)).digest("hex");
|
|
44601
|
-
} catch {
|
|
44602
|
-
return null;
|
|
44603
|
-
}
|
|
44604
|
-
hashes.set(file, { size: stats.size, mtimeMs: stats.mtimeMs, md5 });
|
|
44605
|
-
return md5;
|
|
44606
|
-
}
|
|
44607
|
-
function sectionOf(text, section) {
|
|
44608
|
-
const keys = new Map;
|
|
44609
|
-
let inside = false;
|
|
44610
|
-
for (const line of text.split(/\r?\n/)) {
|
|
44611
|
-
const header = /^\[([^\]]+)\]\s*$/.exec(line);
|
|
44612
|
-
if (header !== null) {
|
|
44613
|
-
inside = header[1] === section;
|
|
44614
|
-
continue;
|
|
44615
|
-
}
|
|
44616
|
-
const pair = inside ? /^([A-Za-z0-9_./]+)=(.*)$/.exec(line) : null;
|
|
44617
|
-
if (pair?.[1] !== undefined && pair[2] !== undefined) {
|
|
44618
|
-
keys.set(pair[1], pair[2]);
|
|
44619
|
-
}
|
|
44620
|
-
}
|
|
44621
|
-
return keys;
|
|
44622
|
-
}
|
|
44623
|
-
function unquoted(value) {
|
|
44624
|
-
return value === undefined ? "" : value.replace(/^"(.*)"$/, "$1");
|
|
44625
|
-
}
|
|
44626
|
-
function listed2(value) {
|
|
44627
|
-
return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
|
|
44628
|
-
}
|
|
44629
|
-
function onDisk(projectPath, resPath) {
|
|
44630
|
-
return join33(projectPath, resPath.slice("res://".length));
|
|
44631
|
-
}
|
|
44632
|
-
function behind(projectPath, resPath, sidecar) {
|
|
44633
|
-
const remap = sectionOf(sidecar, "remap");
|
|
44634
|
-
const importer = unquoted(remap.get("importer"));
|
|
44635
|
-
if (importer === "keep" || importer === "skip") {
|
|
44636
|
-
return null;
|
|
44637
|
-
}
|
|
44638
|
-
const outputs = listed2(sectionOf(sidecar, "deps").get("dest_files"));
|
|
44639
|
-
if (outputs.some((output) => !existsSync28(onDisk(projectPath, output)))) {
|
|
44640
|
-
return "its imported output is not on disk";
|
|
44641
|
-
}
|
|
44642
|
-
const name = resPath.slice(resPath.lastIndexOf("/") + 1);
|
|
44643
|
-
const record = join33(projectPath, ".godot", "imported", `${name}-${createHash6("md5").update(resPath, "utf8").digest("hex")}.md5`);
|
|
44644
|
-
let recorded = "";
|
|
44645
|
-
try {
|
|
44646
|
-
recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync29(record, "utf8"))?.[1] ?? "";
|
|
44647
|
-
} catch {
|
|
44648
|
-
recorded = "";
|
|
44649
|
-
}
|
|
44650
|
-
if (recorded === "") {
|
|
44651
|
-
return "its import recorded no hash of its source";
|
|
44652
|
-
}
|
|
44653
|
-
const current = md5Of(onDisk(projectPath, resPath));
|
|
44654
|
-
return current === null || current === recorded ? null : "the source changed after it was imported";
|
|
44655
|
-
}
|
|
44656
|
-
function staleImports(projectPath) {
|
|
44657
|
-
const stale = [];
|
|
44658
|
-
const visit = (directory, resDirectory) => {
|
|
44659
|
-
if (existsSync28(join33(directory, ".gdignore")) || existsSync28(join33(directory, VERSION_MARKER))) {
|
|
44660
|
-
return;
|
|
44661
|
-
}
|
|
44662
|
-
let entries;
|
|
44663
|
-
try {
|
|
44664
|
-
entries = readdirSync15(directory, { withFileTypes: true });
|
|
44665
|
-
} catch {
|
|
44666
|
-
return;
|
|
44667
|
-
}
|
|
44668
|
-
const names = new Set(entries.map((entry) => entry.name));
|
|
44669
|
-
for (const entry of entries) {
|
|
44670
|
-
if (entry.name.startsWith(".")) {
|
|
44671
|
-
continue;
|
|
44672
|
-
}
|
|
44673
|
-
const resPath = `${resDirectory}${entry.name}`;
|
|
44674
|
-
if (entry.isDirectory()) {
|
|
44675
|
-
visit(join33(directory, entry.name), `${resPath}/`);
|
|
44676
|
-
continue;
|
|
44677
|
-
}
|
|
44678
|
-
if (!entry.isFile()) {
|
|
44679
|
-
continue;
|
|
44680
|
-
}
|
|
44681
|
-
if (entry.name.endsWith(".import")) {
|
|
44682
|
-
const source = resPath.slice(0, -".import".length);
|
|
44683
|
-
if (!names.has(entry.name.slice(0, -".import".length))) {
|
|
44684
|
-
continue;
|
|
44685
|
-
}
|
|
44686
|
-
let sidecar;
|
|
44687
|
-
try {
|
|
44688
|
-
sidecar = readFileSync29(join33(directory, entry.name), "utf8");
|
|
44689
|
-
} catch {
|
|
44690
|
-
continue;
|
|
44691
|
-
}
|
|
44692
|
-
const reason = behind(projectPath, source, sidecar);
|
|
44693
|
-
if (reason !== null) {
|
|
44694
|
-
stale.push({ path: source, reason });
|
|
44695
|
-
}
|
|
44696
|
-
continue;
|
|
44697
|
-
}
|
|
44698
|
-
const extension = entry.name.slice(entry.name.lastIndexOf(".") + 1).toLowerCase();
|
|
44699
|
-
if (IMPORTABLE.has(extension) && !names.has(`${entry.name}.import`)) {
|
|
44700
|
-
stale.push({ path: resPath, reason: "it has not been imported" });
|
|
44701
|
-
}
|
|
44702
|
-
}
|
|
44703
|
-
};
|
|
44704
|
-
visit(projectPath, "res://");
|
|
44705
|
-
return stale.sort((one, other) => one.path.localeCompare(other.path));
|
|
44706
|
-
}
|
|
44707
|
-
var IMPORTABLE_EXTENSIONS, IMPORTABLE, hashes;
|
|
44708
|
-
var init_import_freshness = __esm(() => {
|
|
44709
|
-
init_project_scan();
|
|
44710
|
-
IMPORTABLE_EXTENSIONS = [
|
|
44711
|
-
"png",
|
|
44712
|
-
"jpg",
|
|
44713
|
-
"jpeg",
|
|
44714
|
-
"webp",
|
|
44715
|
-
"svg",
|
|
44716
|
-
"bmp",
|
|
44717
|
-
"tga",
|
|
44718
|
-
"exr",
|
|
44719
|
-
"hdr",
|
|
44720
|
-
"dds",
|
|
44721
|
-
"ktx",
|
|
44722
|
-
"wav",
|
|
44723
|
-
"mp3",
|
|
44724
|
-
"ogg",
|
|
44725
|
-
"ttf",
|
|
44726
|
-
"otf",
|
|
44727
|
-
"ttc",
|
|
44728
|
-
"otc",
|
|
44729
|
-
"woff",
|
|
44730
|
-
"woff2",
|
|
44731
|
-
"pfb",
|
|
44732
|
-
"pfm",
|
|
44733
|
-
"fnt",
|
|
44734
|
-
"font",
|
|
44735
|
-
"glb",
|
|
44736
|
-
"gltf",
|
|
44737
|
-
"fbx",
|
|
44738
|
-
"obj",
|
|
44739
|
-
"dae"
|
|
44740
|
-
];
|
|
44741
|
-
IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
|
|
44742
|
-
hashes = new Map;
|
|
44743
|
-
});
|
|
44744
|
-
|
|
44745
44855
|
// src/keeper-channel.ts
|
|
44746
44856
|
import { connect as connect2, createServer as createServer2 } from "node:net";
|
|
44747
44857
|
async function askTheKeeperToStop(address, withinMs) {
|
|
@@ -46676,6 +46786,7 @@ function settingsNamedBy(op, args) {
|
|
|
46676
46786
|
const text = (key) => readString(args, key) ?? "";
|
|
46677
46787
|
switch (op) {
|
|
46678
46788
|
case "set":
|
|
46789
|
+
case "unset":
|
|
46679
46790
|
return text("setting") === "" ? [] : [text("setting")];
|
|
46680
46791
|
case "add_autoload":
|
|
46681
46792
|
case "remove_autoload":
|
|
@@ -47178,7 +47289,7 @@ function scriptsToDiagnose(projectPath, given) {
|
|
|
47178
47289
|
found.push(relative);
|
|
47179
47290
|
continue;
|
|
47180
47291
|
}
|
|
47181
|
-
const inside =
|
|
47292
|
+
const inside = folderFiles(location.absolutePath, [".gd"]).map((script) => relative === "" ? script : `${relative}/${script}`);
|
|
47182
47293
|
if (inside.length === 0) {
|
|
47183
47294
|
empty.push(entry);
|
|
47184
47295
|
}
|
|
@@ -47194,7 +47305,7 @@ function engineChecked(why) {
|
|
|
47194
47305
|
return {
|
|
47195
47306
|
checkedBy: "engine",
|
|
47196
47307
|
languageServer: why,
|
|
47197
|
-
note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, and no warnings, which the engine tells only an editor: warnings is null rather than 0. editor_launch with hidden: true opens this project's editor without a window, and its language server reports both."
|
|
47308
|
+
note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, and no warnings, which the engine tells only an editor: warnings is null rather than 0. editor_launch with hidden: true opens this project's editor without a window, and its language server reports both. Until then clean says only that every script compiles."
|
|
47198
47309
|
};
|
|
47199
47310
|
}
|
|
47200
47311
|
function isDirectory(path) {
|
|
@@ -47204,24 +47315,6 @@ function isDirectory(path) {
|
|
|
47204
47315
|
return false;
|
|
47205
47316
|
}
|
|
47206
47317
|
}
|
|
47207
|
-
function scriptsUnder(directory, prefix) {
|
|
47208
|
-
if (existsSync30(join38(directory, ".gdignore"))) {
|
|
47209
|
-
return [];
|
|
47210
|
-
}
|
|
47211
|
-
const found = [];
|
|
47212
|
-
for (const entry of readdirSync17(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
47213
|
-
if (entry.name.startsWith(".")) {
|
|
47214
|
-
continue;
|
|
47215
|
-
}
|
|
47216
|
-
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
47217
|
-
if (entry.isDirectory()) {
|
|
47218
|
-
found.push(...scriptsUnder(join38(directory, entry.name), path));
|
|
47219
|
-
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
47220
|
-
found.push(path);
|
|
47221
|
-
}
|
|
47222
|
-
}
|
|
47223
|
-
return found;
|
|
47224
|
-
}
|
|
47225
47318
|
function mainSceneOf(projectFile) {
|
|
47226
47319
|
const scene = parseProjectGodot(readFileSync34(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
47227
47320
|
return typeof scene === "string" && scene !== "" ? scene : null;
|
|
@@ -48058,6 +48151,8 @@ class GodotServer {
|
|
|
48058
48151
|
return await editorOrEngine("create_animation", "writes");
|
|
48059
48152
|
case "add_track":
|
|
48060
48153
|
return await editorOrEngine("add_animation_track", "writes");
|
|
48154
|
+
case "remove_track":
|
|
48155
|
+
return await editorOrEngine("remove_animation_track", "writes");
|
|
48061
48156
|
case "set":
|
|
48062
48157
|
return await editorOrEngine("set_animation", "writes");
|
|
48063
48158
|
case "rename":
|
|
@@ -48066,6 +48161,10 @@ class GodotServer {
|
|
|
48066
48161
|
return await editorOrEngine("remove_animation", "writes");
|
|
48067
48162
|
case "add_state":
|
|
48068
48163
|
return await editorOrEngine("add_animation_state", "writes");
|
|
48164
|
+
case "remove_state":
|
|
48165
|
+
return await editorOrEngine("remove_animation_state", "writes");
|
|
48166
|
+
case "disconnect_states":
|
|
48167
|
+
return await editorOrEngine("disconnect_animation_states", "writes");
|
|
48069
48168
|
default:
|
|
48070
48169
|
return await editorOrEngine("connect_animation_states", "writes");
|
|
48071
48170
|
}
|
|
@@ -49410,7 +49509,7 @@ class GodotServer {
|
|
|
49410
49509
|
const reportsDir = join38(project.value.path, ".godot", ...ours.split("/"));
|
|
49411
49510
|
let report = null;
|
|
49412
49511
|
let reportProblem = null;
|
|
49413
|
-
const casePrinted = readRun(existsSync30(engineLog) ? readFileSync34(engineLog, "utf8") : "");
|
|
49512
|
+
const casePrinted = withNestedErrors(readRun(existsSync30(engineLog) ? readFileSync34(engineLog, "utf8") : ""), run.log.all);
|
|
49414
49513
|
const declaredErrors = readExpected(expectedFile);
|
|
49415
49514
|
try {
|
|
49416
49515
|
const written = existsSync30(reportsDir) && notCompiled === null ? readdirSync17(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
@@ -49525,7 +49624,7 @@ class GodotServer {
|
|
|
49525
49624
|
path: suite.path,
|
|
49526
49625
|
name: report.suites.find((entry) => entry.path === suite.path)?.name ?? suite.path,
|
|
49527
49626
|
orphans: suite.orphans,
|
|
49528
|
-
message: `${suite.orphans}
|
|
49627
|
+
message: `${suite.orphans === 1 ? "1 node" : `${String(suite.orphans)} nodes`} built during this suite ${suite.orphans === 1 ? "was" : "were"} neither freed nor in the tree when it finished, which gdUnit4 counts as orphans. Free each one, or make it with auto_free(), which frees it once the case is over.`
|
|
49529
49628
|
}));
|
|
49530
49629
|
const judgedErrors = unexpectedErrors(casePrinted, declaredErrors, [...casePrinted.suites.keys()]);
|
|
49531
49630
|
const uncaughtTexts = new Set(uncaught.map((entry) => entry.text));
|
|
@@ -49555,7 +49654,8 @@ ${error.text}`;
|
|
|
49555
49654
|
const declaredNotPrinted = judgedErrors.neverPrinted.map((missing) => ({
|
|
49556
49655
|
suite: missing.suite,
|
|
49557
49656
|
case: missing.during,
|
|
49558
|
-
text: missing.text
|
|
49657
|
+
text: missing.text,
|
|
49658
|
+
printedInstead: judgedErrors.printed.find((error) => error.suite === missing.suite && error.during === missing.during && error.text.includes(missing.text))?.text
|
|
49559
49659
|
}));
|
|
49560
49660
|
const erring = (path) => ({
|
|
49561
49661
|
undeclared: undeclaredPrinted.filter((error) => error.suite === path).length,
|
|
@@ -49580,6 +49680,9 @@ ${error.text}`;
|
|
|
49580
49680
|
const uncaughtFolded = foldedRepeats(uncaught.map(aboveTheRunner));
|
|
49581
49681
|
const listedAsUncaught = new Set(uncaughtFolded.map((entry) => entry.index));
|
|
49582
49682
|
const notApplied = ignoredYetRan(report, ignored);
|
|
49683
|
+
const scripts = ignored.length === 0 ? null : new ProjectScripts(project.value.path);
|
|
49684
|
+
const ignoreRead = ignoredLeftOut(report, ignored, asked.flatMap((path) => scripts === null ? [] : path.endsWith(".gd") ? scripts.suite(path) ?? [] : scripts.suitesUnder([path])));
|
|
49685
|
+
const unmatched = ignoreRead.unmatched.filter((entry) => !notApplied.some((one) => one.entry === entry));
|
|
49583
49686
|
const testsNotRun = [
|
|
49584
49687
|
...new Set((selection.value?.selected ?? []).filter((one) => !report.suites.some((suite) => (suite.path === one.suite || suite.name === basename12(one.suite, ".gd")) && suite.cases.some((ran) => ran.name === one.test || new RegExp(`^${one.test}\\W`).test(ran.name)))).map((one) => `${basename12(one.suite, ".gd")}:${one.test}`))
|
|
49585
49688
|
];
|
|
@@ -49613,6 +49716,8 @@ ${error.text}`;
|
|
|
49613
49716
|
orphans: orphans.total > 0 ? orphans.total : undefined,
|
|
49614
49717
|
notRun: notRun > 0 ? notRun : undefined,
|
|
49615
49718
|
ignoreNotApplied: notApplied.length > 0 ? notApplied : undefined,
|
|
49719
|
+
ignored: ignoreRead.leftOut.length > 0 ? ignoreRead.leftOut : undefined,
|
|
49720
|
+
ignoreUnmatched: unmatched.length > 0 ? unmatched : undefined,
|
|
49616
49721
|
...printedLines,
|
|
49617
49722
|
savesNote,
|
|
49618
49723
|
...scanned,
|
|
@@ -49621,6 +49726,7 @@ ${error.text}`;
|
|
|
49621
49726
|
testsNotRun.length > 0 ? `${testsNotRun.join(", ")} ${testsNotRun.length === 1 ? "was" : "were"} asked for and not run, so the run is not called passed: gdUnit4 runs a test_ function only in a script that extends GdUnitTestSuite, and leaves out a suite whose script does not load.` : undefined,
|
|
49622
49727
|
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,
|
|
49623
49728
|
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,
|
|
49729
|
+
unmatched.length > 0 ? `${unmatched.join(", ")} ${unmatched.length === 1 ? "names" : "name"} no suite or case under the paths run, so ${unmatched.length === 1 ? "it" : "they"} left nothing out.` : undefined,
|
|
49624
49730
|
uncaughtNote(uncaught),
|
|
49625
49731
|
undeclaredErrors.length > 0 ? `An engine error a suite does not declare fails the run, as gdharness test judges it; ${declaringAdvice(undeclaredErrors[0]?.case ?? "test_name")}, once for each time it prints it.` : undefined,
|
|
49626
49732
|
declaredNotPrinted.length > 0 ? "A declared error the case did not print fails the run too, since the case no longer reaches what it was written to test." : undefined,
|
|
@@ -53380,6 +53486,24 @@ ${error.text}`;
|
|
|
53380
53486
|
}
|
|
53381
53487
|
return session.whereItStopped();
|
|
53382
53488
|
}
|
|
53489
|
+
async keptHoldOn(endpoint) {
|
|
53490
|
+
const kept = this.keptRun();
|
|
53491
|
+
if (kept === null || this.announcedPidOf(kept) !== endpoint.pid) {
|
|
53492
|
+
return null;
|
|
53493
|
+
}
|
|
53494
|
+
const answered = await askTheKeepersDebugger(kept.keeper, { op: "stack" }, KEEPER_DEBUG_MS);
|
|
53495
|
+
if ("unreached" in answered || !Array.isArray(answered["stack"])) {
|
|
53496
|
+
return null;
|
|
53497
|
+
}
|
|
53498
|
+
const top = answered["stack"][0];
|
|
53499
|
+
const held = answered["held"];
|
|
53500
|
+
const reason = typeof held === "object" && held !== null && typeof held["reason"] === "string" ? ` (${String(held["reason"])})` : "";
|
|
53501
|
+
if (typeof top !== "object" || top === null) {
|
|
53502
|
+
return `held by its debugger between frames${reason}`;
|
|
53503
|
+
}
|
|
53504
|
+
const frame = top;
|
|
53505
|
+
return `held by its debugger at ${String(frame["script"])}:${String(frame["line"])} in ${String(frame["name"])}${reason}`;
|
|
53506
|
+
}
|
|
53383
53507
|
async holdOf(run, going) {
|
|
53384
53508
|
if (!run.throughEditor || !(going ?? await this.runStillGoing(run))) {
|
|
53385
53509
|
return { heldAt: null };
|
|
@@ -53495,6 +53619,14 @@ ${error.text}`;
|
|
|
53495
53619
|
"editor_output says heldAt for the run, and editor_run stop ends it"
|
|
53496
53620
|
]);
|
|
53497
53621
|
}
|
|
53622
|
+
const keptHeld = await this.keptHoldOn(choice.endpoint);
|
|
53623
|
+
if (keptHeld !== null) {
|
|
53624
|
+
return this.createErrorResponse(`The game (pid ${choice.endpoint.pid}) is ${keptHeld}, and answers no runtime call while it is.`, [
|
|
53625
|
+
"debug_control continue lets it go, after which this call answers",
|
|
53626
|
+
"debug_state stack reads where it is held, and debug_state variables what it is holding there",
|
|
53627
|
+
"editor_run stop ends it"
|
|
53628
|
+
]);
|
|
53629
|
+
}
|
|
53498
53630
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
53499
53631
|
const saveTo = expectsScreenshot && typeof outputPath === "string" ? outputPath : null;
|
|
53500
53632
|
const sendsThePicture = readBoolean(callArgs, "inline") !== false;
|
|
@@ -53538,12 +53670,14 @@ ${error.text}`;
|
|
|
53538
53670
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, expectsScreenshot ? () => discard(screenshotDir) : "keep");
|
|
53539
53671
|
savedLater = !reply.ok && reply.stillWithTheGame === true;
|
|
53540
53672
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
53673
|
+
const heldSince = await this.keptHoldOn(choice.endpoint);
|
|
53541
53674
|
return this.jsonTextResponse({
|
|
53542
53675
|
pending: true,
|
|
53543
53676
|
requestId: reply.requestId,
|
|
53544
53677
|
command,
|
|
53545
53678
|
waitedMs: timeoutMs,
|
|
53546
|
-
|
|
53679
|
+
...heldSince === null ? {} : { heldAt: heldSince },
|
|
53680
|
+
note: heldSince === null ? `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}` : `The game is ${heldSince}, so it answers once it goes on: debug_control continue lets it go, and runtime_invoke op result with requestId ${reply.requestId} then answers with what the call returned.`,
|
|
53547
53681
|
...answeredBy,
|
|
53548
53682
|
...alongside()
|
|
53549
53683
|
});
|
|
@@ -53807,6 +53941,7 @@ var init_server = __esm(() => {
|
|
|
53807
53941
|
});
|
|
53808
53942
|
PROJECT_INFO_SECTIONS2 = dictionary({
|
|
53809
53943
|
autoloads: { operation: "list_autoloads", params: () => ({}) },
|
|
53944
|
+
classes: { operation: "list_script_classes", params: () => ({}) },
|
|
53810
53945
|
input_actions: { operation: "list_input_actions", params: () => ({}) },
|
|
53811
53946
|
plugins: { operation: "list_plugins", params: () => ({}) },
|
|
53812
53947
|
export_presets: { operation: "list_export_presets", params: () => ({}) },
|
|
@@ -54819,10 +54954,10 @@ init_setup();
|
|
|
54819
54954
|
init_setup();
|
|
54820
54955
|
init_tool_definitions();
|
|
54821
54956
|
init_tool_reference();
|
|
54822
|
-
import { existsSync as
|
|
54823
|
-
import { basename as basename7, dirname as dirname9, join as
|
|
54957
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
54958
|
+
import { basename as basename7, dirname as dirname9, join as join17 } from "node:path";
|
|
54824
54959
|
var SKILL_NAME = "gdharness";
|
|
54825
|
-
var SHARED_SKILLS =
|
|
54960
|
+
var SHARED_SKILLS = join17(".agents", "skills");
|
|
54826
54961
|
function skillMarkdown(version) {
|
|
54827
54962
|
return `---
|
|
54828
54963
|
name: ${SKILL_NAME}
|
|
@@ -55059,27 +55194,27 @@ what they report while starting comes back under \`engine_messages_before_operat
|
|
|
55059
55194
|
function skillFiles(version) {
|
|
55060
55195
|
return new Map([
|
|
55061
55196
|
["SKILL.md", skillMarkdown(version)],
|
|
55062
|
-
[
|
|
55197
|
+
[join17("references", "tools.md"), renderToolsMarkdown()]
|
|
55063
55198
|
]);
|
|
55064
55199
|
}
|
|
55065
55200
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
55066
|
-
const directories = [
|
|
55201
|
+
const directories = [join17(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
55067
55202
|
for (const harness of harnesses) {
|
|
55068
55203
|
if (harness.skills === undefined) {
|
|
55069
55204
|
continue;
|
|
55070
55205
|
}
|
|
55071
|
-
const own =
|
|
55072
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
55073
|
-
directories.push(
|
|
55206
|
+
const own = join17(projectPath, harness.skills.dir);
|
|
55207
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join17(own, SKILL_NAME))) {
|
|
55208
|
+
directories.push(join17(own, SKILL_NAME));
|
|
55074
55209
|
}
|
|
55075
55210
|
}
|
|
55076
55211
|
return directories;
|
|
55077
55212
|
}
|
|
55078
55213
|
function everySkillDirectory(projectPath, harnesses) {
|
|
55079
|
-
const directories = new Set([
|
|
55214
|
+
const directories = new Set([join17(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
55080
55215
|
for (const harness of harnesses) {
|
|
55081
55216
|
if (harness.skills !== undefined) {
|
|
55082
|
-
directories.add(
|
|
55217
|
+
directories.add(join17(projectPath, harness.skills.dir, SKILL_NAME));
|
|
55083
55218
|
}
|
|
55084
55219
|
}
|
|
55085
55220
|
return [...directories];
|
|
@@ -55087,7 +55222,7 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
55087
55222
|
function removeSkill(directories, projectPath) {
|
|
55088
55223
|
const removed = [];
|
|
55089
55224
|
for (const directory of directories) {
|
|
55090
|
-
if (
|
|
55225
|
+
if (existsSync15(directory)) {
|
|
55091
55226
|
rmSync6(directory, { recursive: true, force: true });
|
|
55092
55227
|
pruneEmpty(dirname9(directory), projectPath);
|
|
55093
55228
|
removed.push(directory);
|
|
@@ -55096,10 +55231,10 @@ function removeSkill(directories, projectPath) {
|
|
|
55096
55231
|
return removed;
|
|
55097
55232
|
}
|
|
55098
55233
|
function pruneEmpty(directory, projectPath) {
|
|
55099
|
-
const shared =
|
|
55234
|
+
const shared = join17(projectPath, ".agents");
|
|
55100
55235
|
let path = directory;
|
|
55101
55236
|
while (path !== projectPath && (basename7(path) === "skills" || path === shared)) {
|
|
55102
|
-
if (
|
|
55237
|
+
if (readdirSync10(path).length > 0) {
|
|
55103
55238
|
return;
|
|
55104
55239
|
}
|
|
55105
55240
|
rmSync6(path, { recursive: true, force: true });
|
|
@@ -55109,10 +55244,10 @@ function pruneEmpty(directory, projectPath) {
|
|
|
55109
55244
|
function writeSkill(directories, version) {
|
|
55110
55245
|
const files = skillFiles(version);
|
|
55111
55246
|
return directories.map((directory) => {
|
|
55112
|
-
const replaced =
|
|
55247
|
+
const replaced = existsSync15(directory);
|
|
55113
55248
|
rmSync6(directory, { recursive: true, force: true });
|
|
55114
55249
|
for (const [name, contents] of files) {
|
|
55115
|
-
const path =
|
|
55250
|
+
const path = join17(directory, name);
|
|
55116
55251
|
mkdirSync5(dirname9(path), { recursive: true });
|
|
55117
55252
|
writeFileSync7(path, contents, "utf8");
|
|
55118
55253
|
}
|
|
@@ -55125,27 +55260,27 @@ init_alive();
|
|
|
55125
55260
|
init_desktop();
|
|
55126
55261
|
import {
|
|
55127
55262
|
closeSync as closeSync3,
|
|
55128
|
-
existsSync as
|
|
55263
|
+
existsSync as existsSync19,
|
|
55129
55264
|
fstatSync,
|
|
55130
55265
|
mkdirSync as mkdirSync8,
|
|
55131
55266
|
openSync as openSync3,
|
|
55132
|
-
readdirSync as
|
|
55133
|
-
readFileSync as
|
|
55267
|
+
readdirSync as readdirSync12,
|
|
55268
|
+
readFileSync as readFileSync19,
|
|
55134
55269
|
readSync,
|
|
55135
55270
|
rmSync as rmSync10,
|
|
55136
|
-
statSync as
|
|
55271
|
+
statSync as statSync12,
|
|
55137
55272
|
writeFileSync as writeFileSync10
|
|
55138
55273
|
} from "node:fs";
|
|
55139
55274
|
import { availableParallelism, freemem, totalmem } from "node:os";
|
|
55140
|
-
import { isAbsolute as isAbsolute4, join as
|
|
55275
|
+
import { isAbsolute as isAbsolute4, join as join22, relative as relative3, resolve as resolve4, sep as sep2 } from "node:path";
|
|
55141
55276
|
import process12 from "node:process";
|
|
55142
55277
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
55143
55278
|
|
|
55144
55279
|
// src/engine-slots.ts
|
|
55145
55280
|
init_alive();
|
|
55146
55281
|
init_process_children();
|
|
55147
|
-
import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readFileSync as
|
|
55148
|
-
import { join as
|
|
55282
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readFileSync as readFileSync14, rmSync as rmSync8, statSync as statSync10, writeSync as writeSync2 } from "node:fs";
|
|
55283
|
+
import { join as join19 } from "node:path";
|
|
55149
55284
|
import process11 from "node:process";
|
|
55150
55285
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
55151
55286
|
var START_TOLERANCE_MS = 2000;
|
|
@@ -55196,7 +55331,7 @@ class EngineSlots {
|
|
|
55196
55331
|
for (;; ) {
|
|
55197
55332
|
signal?.throwIfAborted();
|
|
55198
55333
|
for (let slot = 0;slot < this.count; slot += 1) {
|
|
55199
|
-
const lock =
|
|
55334
|
+
const lock = join19(this.directory, `${String(slot)}.lock`);
|
|
55200
55335
|
if (this.made(lock) || await this.takenOver(lock)) {
|
|
55201
55336
|
this.held.add(lock);
|
|
55202
55337
|
return lock;
|
|
@@ -55213,7 +55348,7 @@ class EngineSlots {
|
|
|
55213
55348
|
release(lock) {
|
|
55214
55349
|
this.held.delete(lock);
|
|
55215
55350
|
try {
|
|
55216
|
-
if (holderOf(
|
|
55351
|
+
if (holderOf(readFileSync14(lock, "utf8"))?.pid === this.holder.pid) {
|
|
55217
55352
|
rmSync8(lock, { force: true });
|
|
55218
55353
|
}
|
|
55219
55354
|
} catch {}
|
|
@@ -55239,7 +55374,7 @@ class EngineSlots {
|
|
|
55239
55374
|
const steal = `${lock}.steal`;
|
|
55240
55375
|
if (!this.made(steal)) {
|
|
55241
55376
|
try {
|
|
55242
|
-
if (Date.now() -
|
|
55377
|
+
if (Date.now() - statSync10(steal).mtimeMs > UNWRITTEN_MS) {
|
|
55243
55378
|
rmSync8(steal, { force: true });
|
|
55244
55379
|
}
|
|
55245
55380
|
} catch {}
|
|
@@ -55263,8 +55398,8 @@ class EngineSlots {
|
|
|
55263
55398
|
let content;
|
|
55264
55399
|
let writtenAt;
|
|
55265
55400
|
try {
|
|
55266
|
-
content =
|
|
55267
|
-
writtenAt =
|
|
55401
|
+
content = readFileSync14(lock, "utf8");
|
|
55402
|
+
writtenAt = statSync10(lock).mtimeMs;
|
|
55268
55403
|
} catch {
|
|
55269
55404
|
return false;
|
|
55270
55405
|
}
|
|
@@ -55291,9 +55426,9 @@ init_spawn();
|
|
|
55291
55426
|
|
|
55292
55427
|
// src/suite-fingerprint.ts
|
|
55293
55428
|
init_class_cache();
|
|
55294
|
-
import { createHash as
|
|
55295
|
-
import { existsSync as
|
|
55296
|
-
import { join as
|
|
55429
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
55430
|
+
import { existsSync as existsSync17, readdirSync as readdirSync11, readFileSync as readFileSync15, statSync as statSync11 } from "node:fs";
|
|
55431
|
+
import { join as join20, posix as posix2 } from "node:path";
|
|
55297
55432
|
var RES_LITERAL = /res:\/\/[^"'\s)\]<>]*/g;
|
|
55298
55433
|
var RELATIVE_LOAD = /\b(?:pre)?load\(\s*["']((?!res:\/\/|uid:\/\/)[^"']+)["']/g;
|
|
55299
55434
|
var REACHES_AT_RUN_TIME = [
|
|
@@ -55313,7 +55448,7 @@ class SuiteFingerprints {
|
|
|
55313
55448
|
constructor(scripts, always) {
|
|
55314
55449
|
this.scripts = scripts;
|
|
55315
55450
|
this.everywhere = ["res://project.godot", ...extensionsOf(scripts.projectPath)];
|
|
55316
|
-
this.alwaysDigest =
|
|
55451
|
+
this.alwaysDigest = createHash6("sha256").update(always.join(`
|
|
55317
55452
|
`)).digest("hex");
|
|
55318
55453
|
}
|
|
55319
55454
|
of(suite) {
|
|
@@ -55330,7 +55465,7 @@ class SuiteFingerprints {
|
|
|
55330
55465
|
}
|
|
55331
55466
|
pending.push(...reached.reaches);
|
|
55332
55467
|
}
|
|
55333
|
-
const hash =
|
|
55468
|
+
const hash = createHash6("sha256").update(this.alwaysDigest);
|
|
55334
55469
|
for (const path of [...seen].sort()) {
|
|
55335
55470
|
hash.update(`${path}
|
|
55336
55471
|
${this.read(path).digest}
|
|
@@ -55349,20 +55484,20 @@ ${this.read(path).digest}
|
|
|
55349
55484
|
return reached;
|
|
55350
55485
|
}
|
|
55351
55486
|
addon(root) {
|
|
55352
|
-
const hash =
|
|
55487
|
+
const hash = createHash6("sha256");
|
|
55353
55488
|
const walk = (directory, under) => {
|
|
55354
|
-
for (const entry of
|
|
55355
|
-
const file =
|
|
55489
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
55490
|
+
const file = join20(directory, entry.name);
|
|
55356
55491
|
if (entry.isDirectory()) {
|
|
55357
55492
|
walk(file, `${under}${entry.name}/`);
|
|
55358
55493
|
} else if (entry.isFile()) {
|
|
55359
55494
|
hash.update(`${under}${entry.name}
|
|
55360
|
-
`).update(
|
|
55495
|
+
`).update(readFileSync15(file));
|
|
55361
55496
|
}
|
|
55362
55497
|
}
|
|
55363
55498
|
};
|
|
55364
|
-
const directory =
|
|
55365
|
-
if (
|
|
55499
|
+
const directory = join20(this.scripts.projectPath, root.slice("res://".length));
|
|
55500
|
+
if (existsSync17(directory)) {
|
|
55366
55501
|
walk(directory, "");
|
|
55367
55502
|
}
|
|
55368
55503
|
return { digest: hash.digest("hex"), reaches: [], unreadable: null };
|
|
@@ -55371,26 +55506,26 @@ ${this.read(path).digest}
|
|
|
55371
55506
|
if (PLACEHOLDER.test(path)) {
|
|
55372
55507
|
return { digest: "", reaches: [], unreadable: `${path} is filled in at run time` };
|
|
55373
55508
|
}
|
|
55374
|
-
const file =
|
|
55375
|
-
if (!
|
|
55509
|
+
const file = join20(this.scripts.projectPath, path.slice("res://".length));
|
|
55510
|
+
if (!existsSync17(file)) {
|
|
55376
55511
|
return { digest: "missing", reaches: [], unreadable: null };
|
|
55377
55512
|
}
|
|
55378
|
-
if (
|
|
55379
|
-
const inside =
|
|
55513
|
+
if (statSync11(file).isDirectory()) {
|
|
55514
|
+
const inside = readdirSync11(file, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".")).map((entry) => `${path.endsWith("/") ? path : `${path}/`}${entry.name}`).sort();
|
|
55380
55515
|
return { digest: inside.join(`
|
|
55381
55516
|
`), reaches: inside, unreadable: null };
|
|
55382
55517
|
}
|
|
55383
55518
|
if (BINARY_RESOURCE.test(path)) {
|
|
55384
55519
|
return { digest: "", reaches: [], unreadable: `${path} is a binary resource` };
|
|
55385
55520
|
}
|
|
55386
|
-
const bytes =
|
|
55387
|
-
const digest =
|
|
55521
|
+
const bytes = readFileSync15(file);
|
|
55522
|
+
const digest = createHash6("sha256").update(bytes).digest("hex");
|
|
55388
55523
|
const text = bytes.toString("latin1");
|
|
55389
55524
|
const reaches = new Set;
|
|
55390
55525
|
for (const [literal] of text.matchAll(RES_LITERAL)) {
|
|
55391
55526
|
reaches.add(literal);
|
|
55392
55527
|
}
|
|
55393
|
-
if (
|
|
55528
|
+
if (existsSync17(`${file}.import`)) {
|
|
55394
55529
|
reaches.add(`${path}.import`);
|
|
55395
55530
|
}
|
|
55396
55531
|
if (path.endsWith(".gd")) {
|
|
@@ -55420,8 +55555,8 @@ ${this.read(path).digest}
|
|
|
55420
55555
|
}
|
|
55421
55556
|
}
|
|
55422
55557
|
function extensionsOf(projectPath) {
|
|
55423
|
-
const list =
|
|
55424
|
-
return
|
|
55558
|
+
const list = join20(projectPath, ".godot", "extension_list.cfg");
|
|
55559
|
+
return existsSync17(list) ? readFileSync15(list, "utf8").split(`
|
|
55425
55560
|
`).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
|
|
55426
55561
|
}
|
|
55427
55562
|
|
|
@@ -55430,18 +55565,18 @@ init_suite_judge();
|
|
|
55430
55565
|
init_suite_plan();
|
|
55431
55566
|
|
|
55432
55567
|
// src/version.ts
|
|
55433
|
-
import { readFileSync as
|
|
55434
|
-
import { dirname as dirname11, join as
|
|
55568
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
55569
|
+
import { dirname as dirname11, join as join21 } from "node:path";
|
|
55435
55570
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
55436
55571
|
function getLocalVersion() {
|
|
55437
55572
|
const currentDirectory = dirname11(fileURLToPath2(import.meta.url));
|
|
55438
55573
|
const packageCandidates = [
|
|
55439
|
-
|
|
55440
|
-
|
|
55574
|
+
join21(currentDirectory, "..", "package.json"),
|
|
55575
|
+
join21(currentDirectory, "..", "..", "package.json")
|
|
55441
55576
|
];
|
|
55442
55577
|
for (const packagePath of packageCandidates) {
|
|
55443
55578
|
try {
|
|
55444
|
-
const value = JSON.parse(
|
|
55579
|
+
const value = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
55445
55580
|
if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
|
|
55446
55581
|
return value.version;
|
|
55447
55582
|
}
|
|
@@ -55493,9 +55628,9 @@ function testOptionsOf(args, cwd) {
|
|
|
55493
55628
|
}
|
|
55494
55629
|
const first = positional[0];
|
|
55495
55630
|
const named = first === undefined ? null : resolve4(cwd, first);
|
|
55496
|
-
const projectNamed = named !== null &&
|
|
55631
|
+
const projectNamed = named !== null && existsSync19(join22(named, "project.godot"));
|
|
55497
55632
|
const projectPath = projectNamed ? named : resolve4(cwd);
|
|
55498
|
-
if (!
|
|
55633
|
+
if (!existsSync19(join22(projectPath, "project.godot"))) {
|
|
55499
55634
|
throw new TestUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
55500
55635
|
}
|
|
55501
55636
|
const last = (flag) => valued.get(flag)?.at(-1);
|
|
@@ -55539,7 +55674,7 @@ function resPathOf(entry, projectPath, cwd) {
|
|
|
55539
55674
|
const file = caseAt?.[1] ?? entry;
|
|
55540
55675
|
const fromProject = resolve4(projectPath, file);
|
|
55541
55676
|
const fromHere = resolve4(cwd, file);
|
|
55542
|
-
const absolute = isAbsolute4(file) || !
|
|
55677
|
+
const absolute = isAbsolute4(file) || !existsSync19(fromProject) && existsSync19(fromHere) ? fromHere : fromProject;
|
|
55543
55678
|
const inside = relative3(projectPath, absolute);
|
|
55544
55679
|
if (inside.startsWith("..") || isAbsolute4(inside)) {
|
|
55545
55680
|
throw new TestUsageError(`${entry} is not inside the project at ${projectPath}.`);
|
|
@@ -55551,7 +55686,7 @@ function homeOf(projectPath) {
|
|
|
55551
55686
|
return mainCheckoutOf(projectPath) ?? projectPath;
|
|
55552
55687
|
}
|
|
55553
55688
|
function selectedSuites(scripts, options) {
|
|
55554
|
-
const roots = options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) =>
|
|
55689
|
+
const roots = options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync19(join22(options.projectPath, root))).map((root) => `res://${root}`);
|
|
55555
55690
|
if (options.selected.length === 0) {
|
|
55556
55691
|
const found = scripts.suitesUnder(roots);
|
|
55557
55692
|
if (found.length === 0) {
|
|
@@ -55563,10 +55698,10 @@ function selectedSuites(scripts, options) {
|
|
|
55563
55698
|
for (const entry of options.selected) {
|
|
55564
55699
|
const caseAt = /^(res:\/\/.*\.gd):(\w+)$/.exec(entry);
|
|
55565
55700
|
const path = caseAt?.[1] ?? entry;
|
|
55566
|
-
const directory =
|
|
55567
|
-
const suites =
|
|
55701
|
+
const directory = join22(options.projectPath, path.slice(6));
|
|
55702
|
+
const suites = existsSync19(directory) && statSync12(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
|
|
55568
55703
|
if (suites.length === 0) {
|
|
55569
|
-
throw new TestUsageError(
|
|
55704
|
+
throw new TestUsageError(existsSync19(directory) ? `${path} holds no gdUnit4 suite: nothing there extends GdUnitTestSuite and declares a test_ function.` : `Nothing at ${path} in the project at ${options.projectPath}.`);
|
|
55570
55705
|
}
|
|
55571
55706
|
for (const suite of suites) {
|
|
55572
55707
|
const picked = chosen.get(suite.path) ?? { suite, cases: new Set };
|
|
@@ -55643,17 +55778,17 @@ async function runTestCommand(options, godotPath) {
|
|
|
55643
55778
|
const startedAt = Date.now();
|
|
55644
55779
|
const scripts = new ProjectScripts(options.projectPath);
|
|
55645
55780
|
const home = homeOf(options.projectPath);
|
|
55646
|
-
const state =
|
|
55647
|
-
const timesFile =
|
|
55648
|
-
const passedFile =
|
|
55781
|
+
const state = join22(home, ".godot", "gdharness");
|
|
55782
|
+
const timesFile = join22(state, "suite-times.json");
|
|
55783
|
+
const passedFile = join22(state, "suite-passed.json");
|
|
55649
55784
|
let suites = selectedSuites(scripts, options);
|
|
55650
55785
|
const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
|
|
55651
55786
|
const declared = new Map(wholeSuites.map((suite) => [suite.path, suite.cases]));
|
|
55652
|
-
const testRuns =
|
|
55787
|
+
const testRuns = join22(options.projectPath, ".godot", "gdharness", "test-runs");
|
|
55653
55788
|
sweepRunDirectories(testRuns);
|
|
55654
|
-
const runDirectory =
|
|
55789
|
+
const runDirectory = join22(testRuns, `${String(startedAt)}-${String(process12.pid)}`);
|
|
55655
55790
|
mkdirSync8(runDirectory, { recursive: true });
|
|
55656
|
-
const slots = new EngineSlots(options.slotsDirectory ??
|
|
55791
|
+
const slots = new EngineSlots(options.slotsDirectory ?? join22(home, ".godot", "gate", "slots"), options.slotCount ?? Math.min(MOST_ENGINES, availableParallelism()), { pid: process12.pid, started: await ownStart() });
|
|
55657
55792
|
const running = new Set;
|
|
55658
55793
|
const interrupted = () => {
|
|
55659
55794
|
for (const end of running) {
|
|
@@ -55682,9 +55817,9 @@ async function runTestCommand(options, godotPath) {
|
|
|
55682
55817
|
}
|
|
55683
55818
|
const fingerprints = new SuiteFingerprints(scripts, [
|
|
55684
55819
|
getLocalVersion(),
|
|
55685
|
-
|
|
55820
|
+
readFileSync19(shippedListedRunner(), "utf8"),
|
|
55686
55821
|
...options.reachAlso.map((path) => `${path}
|
|
55687
|
-
${
|
|
55822
|
+
${existsSync19(path) ? readFileSync19(path, "latin1") : "missing"}`)
|
|
55688
55823
|
]);
|
|
55689
55824
|
const printsOf = new Map(suites.map((suite) => [suite.path, fingerprints.of(suite)]));
|
|
55690
55825
|
if (options.changedOnly) {
|
|
@@ -55709,7 +55844,7 @@ ${existsSync18(path) ? readFileSync18(path, "latin1") : "missing"}`)
|
|
|
55709
55844
|
const plans = options.order === null ? dealt(units, declared, room.engines) : [inOneEngine(ordered(units, options.order), declared)];
|
|
55710
55845
|
const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
|
|
55711
55846
|
const held = options.order === null && room.held !== undefined && plans.length === room.engines ? `, of the ${String(options.engines)} asked for, since ${room.held}` : "";
|
|
55712
|
-
const how = options.order === null ? `${plans
|
|
55847
|
+
const how = options.order === null ? `${windowedSaid(plans)}${held}` : options.order === "reversed" ? ", in reverse order of path" : `, shuffled by ${String(options.order)}`;
|
|
55713
55848
|
console.log(`${plural(functions, "test function")} in ${plural(suites.length, "suite")} over ${plural(plans.length, "engine")}${how}.`);
|
|
55714
55849
|
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path).length > 1);
|
|
55715
55850
|
if (plans.length > 1 && split.length > 0) {
|
|
@@ -55737,7 +55872,7 @@ function plural(count, noun) {
|
|
|
55737
55872
|
}
|
|
55738
55873
|
function readRecord(file) {
|
|
55739
55874
|
try {
|
|
55740
|
-
const parsed = JSON.parse(
|
|
55875
|
+
const parsed = JSON.parse(readFileSync19(file, "utf8"));
|
|
55741
55876
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
55742
55877
|
} catch {
|
|
55743
55878
|
return {};
|
|
@@ -55764,20 +55899,20 @@ function recordPassed(file, ran, printsOf) {
|
|
|
55764
55899
|
writeJson(file, passed);
|
|
55765
55900
|
}
|
|
55766
55901
|
function sweepRunDirectories(testRuns) {
|
|
55767
|
-
if (!
|
|
55902
|
+
if (!existsSync19(testRuns)) {
|
|
55768
55903
|
return;
|
|
55769
55904
|
}
|
|
55770
|
-
for (const entry of
|
|
55905
|
+
for (const entry of readdirSync12(testRuns)) {
|
|
55771
55906
|
const pid = Number(/^\d+-(\d+)$/.exec(entry)?.[1]);
|
|
55772
55907
|
if (!Number.isInteger(pid) || !alive(pid)) {
|
|
55773
|
-
rmSync10(
|
|
55908
|
+
rmSync10(join22(testRuns, entry), { recursive: true, force: true });
|
|
55774
55909
|
}
|
|
55775
55910
|
}
|
|
55776
55911
|
}
|
|
55777
55912
|
async function importProject(launch) {
|
|
55778
|
-
const directory =
|
|
55779
|
-
mkdirSync8(
|
|
55780
|
-
const log =
|
|
55913
|
+
const directory = join22(launch.runDirectory, "import");
|
|
55914
|
+
mkdirSync8(join22(directory, "user"), { recursive: true });
|
|
55915
|
+
const log = join22(directory, "import.log");
|
|
55781
55916
|
const lock = await launch.slots.take();
|
|
55782
55917
|
try {
|
|
55783
55918
|
const ended = await watched(launch, ["--headless", "--audio-driver", "Dummy", "--path", launch.projectPath, "--import"], directory, log, false, launch.silenceFloorSeconds);
|
|
@@ -55790,11 +55925,11 @@ async function importProject(launch) {
|
|
|
55790
55925
|
}
|
|
55791
55926
|
}
|
|
55792
55927
|
async function runEngine2(launch, plan, index, units, again = "") {
|
|
55793
|
-
const directory =
|
|
55794
|
-
mkdirSync8(
|
|
55795
|
-
const suitesFile =
|
|
55796
|
-
const skippingFile =
|
|
55797
|
-
const expectedFile =
|
|
55928
|
+
const directory = join22(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
|
|
55929
|
+
mkdirSync8(join22(directory, "user"), { recursive: true });
|
|
55930
|
+
const suitesFile = join22(directory, "suites.txt");
|
|
55931
|
+
const skippingFile = join22(directory, "skipping.txt");
|
|
55932
|
+
const expectedFile = join22(directory, "expected.json");
|
|
55798
55933
|
writeFileSync10(suitesFile, `${plan.suites.map((suite) => suite.path).join(`
|
|
55799
55934
|
`)}
|
|
55800
55935
|
`);
|
|
@@ -55814,7 +55949,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55814
55949
|
...plan.windowed ? [] : ["--ignoreHeadlessMode"],
|
|
55815
55950
|
"-c",
|
|
55816
55951
|
"-rd",
|
|
55817
|
-
`res://${relative3(launch.projectPath,
|
|
55952
|
+
`res://${relative3(launch.projectPath, join22(directory, "reports")).split(sep2).join("/")}`,
|
|
55818
55953
|
"-rc",
|
|
55819
55954
|
"1",
|
|
55820
55955
|
"--",
|
|
@@ -55831,7 +55966,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55831
55966
|
const held = new Set(plan.suites.map((suite) => suite.path));
|
|
55832
55967
|
const slowest = Math.max(0, ...units.filter((unit) => held.has(unit.suite)).map((unit) => unit.cost));
|
|
55833
55968
|
const silence = Math.max(launch.silenceFloorSeconds, SILENCE_TIMES_SLOWEST * slowest);
|
|
55834
|
-
const log =
|
|
55969
|
+
const log = join22(directory, "engine.log");
|
|
55835
55970
|
const lock = await launch.slots.take();
|
|
55836
55971
|
const began = Date.now();
|
|
55837
55972
|
let end;
|
|
@@ -55840,7 +55975,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55840
55975
|
} finally {
|
|
55841
55976
|
launch.slots.release(lock);
|
|
55842
55977
|
}
|
|
55843
|
-
const printed = readRun(
|
|
55978
|
+
const printed = readRun(readFileSync19(log, "utf8"));
|
|
55844
55979
|
const assignment = { suites: plan.suites, expected: readExpected(expectedFile) };
|
|
55845
55980
|
return {
|
|
55846
55981
|
index,
|
|
@@ -55852,7 +55987,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55852
55987
|
};
|
|
55853
55988
|
}
|
|
55854
55989
|
async function watched(launch, args, directory, log, windowed, silenceSeconds) {
|
|
55855
|
-
const user =
|
|
55990
|
+
const user = join22(directory, "user");
|
|
55856
55991
|
const env = { ...process12.env, APPDATA: user, XDG_DATA_HOME: user };
|
|
55857
55992
|
const output = openSync3(log, "w");
|
|
55858
55993
|
let child;
|
|
@@ -55864,12 +55999,12 @@ async function watched(launch, args, directory, log, windowed, silenceSeconds) {
|
|
|
55864
55999
|
const how = windowed ? "on a desktop of its own" : "through the desktop helper";
|
|
55865
56000
|
return { code: null, signal: null, ended: `the engine could not be started ${how}: ${compiled}` };
|
|
55866
56001
|
}
|
|
55867
|
-
const stopFile =
|
|
56002
|
+
const stopFile = join22(directory, "stop");
|
|
55868
56003
|
const how = windowed ? { desktop: HIDDEN_DESKTOP, directory: launch.projectPath } : { directory: launch.projectPath };
|
|
55869
56004
|
const wrapper = throughHelper(how, launch.godotPath, args, {
|
|
55870
|
-
spec:
|
|
55871
|
-
pid:
|
|
55872
|
-
error:
|
|
56005
|
+
spec: join22(directory, "spec.json"),
|
|
56006
|
+
pid: join22(directory, "pid"),
|
|
56007
|
+
error: join22(directory, "error"),
|
|
55873
56008
|
stop: stopFile
|
|
55874
56009
|
});
|
|
55875
56010
|
child = spawnHidden(wrapper.command, wrapper.args, {
|