gdharness 1.3.7 → 1.3.8
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 +869 -805
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +3 -1
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +47 -23
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +27 -1
- package/build/godot/operations/dependencies.gd +61 -10
- package/build/godot/operations/gdscript_analysis.gd +62 -6
- package/build/godot/operations/gdscript_authoring.gd +4 -1
- package/build/godot/operations/godot_operations.gd +2 -0
- package/build/godot/operations/import_pipeline.gd +45 -1
- package/build/godot/operations/project_diagnostics.gd +6 -5
- package/build/keeper.js +1 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -8367,9 +8367,487 @@ 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
|
+
const found = [];
|
|
8419
|
+
const visit = (directory, prefix) => {
|
|
8420
|
+
if (steppedOver(directory)) {
|
|
8421
|
+
return;
|
|
8422
|
+
}
|
|
8423
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8424
|
+
if (skipped(entry.name)) {
|
|
8425
|
+
continue;
|
|
8426
|
+
}
|
|
8427
|
+
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
8428
|
+
if (entry.isDirectory()) {
|
|
8429
|
+
visit(join7(directory, entry.name), spelled);
|
|
8430
|
+
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
8431
|
+
found.push(spelled);
|
|
8432
|
+
}
|
|
8433
|
+
}
|
|
8434
|
+
};
|
|
8435
|
+
visit(projectPath, "");
|
|
8436
|
+
return found.sort();
|
|
8437
|
+
}
|
|
8438
|
+
function scriptsWithoutUid(projectPath) {
|
|
8439
|
+
const found = [];
|
|
8440
|
+
const visit = (directory, prefix) => {
|
|
8441
|
+
if (steppedOver(directory)) {
|
|
8442
|
+
return;
|
|
8443
|
+
}
|
|
8444
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8445
|
+
if (skipped(entry.name)) {
|
|
8446
|
+
continue;
|
|
8447
|
+
}
|
|
8448
|
+
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
8449
|
+
if (entry.isDirectory()) {
|
|
8450
|
+
visit(join7(directory, entry.name), spelled);
|
|
8451
|
+
continue;
|
|
8452
|
+
}
|
|
8453
|
+
if (!entry.isFile()) {
|
|
8454
|
+
continue;
|
|
8455
|
+
}
|
|
8456
|
+
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
8457
|
+
if (CARRIES_A_UID.has(extension) && !existsSync6(join7(directory, `${entry.name}.uid`))) {
|
|
8458
|
+
found.push(spelled);
|
|
8459
|
+
}
|
|
8460
|
+
}
|
|
8461
|
+
};
|
|
8462
|
+
visit(projectPath, "");
|
|
8463
|
+
return found.sort();
|
|
8464
|
+
}
|
|
8465
|
+
function pathsInUidCache(projectPath) {
|
|
8466
|
+
let data;
|
|
8467
|
+
try {
|
|
8468
|
+
data = readFileSync3(join7(projectPath, ".godot", "uid_cache.bin"));
|
|
8469
|
+
} catch {
|
|
8470
|
+
return null;
|
|
8471
|
+
}
|
|
8472
|
+
if (data.length < 4) {
|
|
8473
|
+
return null;
|
|
8474
|
+
}
|
|
8475
|
+
const count = data.readUInt32LE(0);
|
|
8476
|
+
const paths = new Map;
|
|
8477
|
+
let at = 4;
|
|
8478
|
+
for (let entry = 0;entry < count; entry++) {
|
|
8479
|
+
if (at + 12 > data.length) {
|
|
8480
|
+
return null;
|
|
8481
|
+
}
|
|
8482
|
+
const id = data.readBigUInt64LE(at);
|
|
8483
|
+
const length = data.readUInt32LE(at + 8);
|
|
8484
|
+
at += 12;
|
|
8485
|
+
if (at + length > data.length) {
|
|
8486
|
+
return null;
|
|
8487
|
+
}
|
|
8488
|
+
paths.set(id, data.toString("utf8", at, at + length));
|
|
8489
|
+
at += length;
|
|
8490
|
+
}
|
|
8491
|
+
return new Set(paths.values());
|
|
8492
|
+
}
|
|
8493
|
+
function isBinary(bytes) {
|
|
8494
|
+
return bytes.subarray(0, 8000).includes(0);
|
|
8495
|
+
}
|
|
8496
|
+
function projectTexts(projectPath) {
|
|
8497
|
+
const found = [];
|
|
8498
|
+
const visit = (directory, prefix) => {
|
|
8499
|
+
if (steppedOver(directory)) {
|
|
8500
|
+
return;
|
|
8501
|
+
}
|
|
8502
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8503
|
+
if (skipped(entry.name)) {
|
|
8504
|
+
continue;
|
|
8505
|
+
}
|
|
8506
|
+
const absolute = join7(directory, entry.name);
|
|
8507
|
+
const spelled = `${prefix}${entry.name}`;
|
|
8508
|
+
if (entry.isDirectory()) {
|
|
8509
|
+
visit(absolute, `${spelled}/`);
|
|
8510
|
+
continue;
|
|
8511
|
+
}
|
|
8512
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
8513
|
+
if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
|
|
8514
|
+
continue;
|
|
8515
|
+
}
|
|
8516
|
+
const bytes = readFileSync3(absolute);
|
|
8517
|
+
if (!isBinary(bytes)) {
|
|
8518
|
+
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
8519
|
+
}
|
|
8520
|
+
}
|
|
8521
|
+
};
|
|
8522
|
+
visit(projectPath, "");
|
|
8523
|
+
return found;
|
|
8524
|
+
}
|
|
8525
|
+
function ignoredTexts(projectPath) {
|
|
8526
|
+
const found = [];
|
|
8527
|
+
const visit = (directory, prefix, ignored) => {
|
|
8528
|
+
if (installedByUs(directory)) {
|
|
8529
|
+
return;
|
|
8530
|
+
}
|
|
8531
|
+
const under = ignored || gdignored(directory);
|
|
8532
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8533
|
+
if (skipped(entry.name)) {
|
|
8534
|
+
continue;
|
|
8535
|
+
}
|
|
8536
|
+
const absolute = join7(directory, entry.name);
|
|
8537
|
+
const spelled = `${prefix}${entry.name}`;
|
|
8538
|
+
if (entry.isDirectory()) {
|
|
8539
|
+
visit(absolute, `${spelled}/`, under);
|
|
8540
|
+
continue;
|
|
8541
|
+
}
|
|
8542
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
8543
|
+
if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
|
|
8544
|
+
continue;
|
|
8545
|
+
}
|
|
8546
|
+
const bytes = readFileSync3(absolute);
|
|
8547
|
+
if (!isBinary(bytes)) {
|
|
8548
|
+
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8551
|
+
};
|
|
8552
|
+
visit(projectPath, "", false);
|
|
8553
|
+
return found;
|
|
8554
|
+
}
|
|
8555
|
+
function plainMatch(line, needle, caseSensitive) {
|
|
8556
|
+
if (caseSensitive) {
|
|
8557
|
+
return line.includes(needle) ? [needle] : null;
|
|
8558
|
+
}
|
|
8559
|
+
const lowered = line.toLowerCase();
|
|
8560
|
+
const at = lowered.indexOf(needle);
|
|
8561
|
+
if (at < 0) {
|
|
8562
|
+
return null;
|
|
8563
|
+
}
|
|
8564
|
+
return [lowered.length === line.length ? line.slice(at, at + needle.length) : needle];
|
|
8565
|
+
}
|
|
8566
|
+
function compiledQuery(query, caseSensitive) {
|
|
8567
|
+
try {
|
|
8568
|
+
return new RegExp(query, caseSensitive ? "" : "i");
|
|
8569
|
+
} catch (error) {
|
|
8570
|
+
const why = errorMessage(error).split(": ").at(-1) ?? "";
|
|
8571
|
+
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.`);
|
|
8572
|
+
}
|
|
8573
|
+
}
|
|
8574
|
+
function searchProject(projectPath, options) {
|
|
8575
|
+
const extensions = options.fileTypes === undefined ? null : new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
|
|
8576
|
+
const result = {
|
|
8577
|
+
query: options.query,
|
|
8578
|
+
results: [],
|
|
8579
|
+
summary: { files_searched: 0, files_with_matches: 0, total_matches: 0, truncated: false }
|
|
8580
|
+
};
|
|
8581
|
+
const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
|
|
8582
|
+
const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
|
|
8583
|
+
const full = () => result.summary.truncated;
|
|
8584
|
+
const notSearched = [];
|
|
8585
|
+
const visit = (directory) => {
|
|
8586
|
+
if (steppedOver(directory)) {
|
|
8587
|
+
if (gdignored(directory)) {
|
|
8588
|
+
notSearched.push(`res://${directory.slice(projectPath.length + 1).replace(/\\/g, "/")}/`);
|
|
8589
|
+
}
|
|
8590
|
+
return;
|
|
8591
|
+
}
|
|
8592
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
8593
|
+
if (full()) {
|
|
8594
|
+
return;
|
|
8595
|
+
}
|
|
8596
|
+
if (skipped(entry.name)) {
|
|
8597
|
+
continue;
|
|
8598
|
+
}
|
|
8599
|
+
const entryPath = join7(directory, entry.name);
|
|
8600
|
+
if (entry.isDirectory()) {
|
|
8601
|
+
visit(entryPath);
|
|
8602
|
+
continue;
|
|
8603
|
+
}
|
|
8604
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
8605
|
+
if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
|
|
8606
|
+
continue;
|
|
8607
|
+
}
|
|
8608
|
+
const bytes = readFileSync3(entryPath);
|
|
8609
|
+
if (extensions === null && isBinary(bytes)) {
|
|
8610
|
+
continue;
|
|
8611
|
+
}
|
|
8612
|
+
result.summary.files_searched += 1;
|
|
8613
|
+
const matches = [];
|
|
8614
|
+
for (const [index, line] of bytes.toString("utf8").split(/\r?\n/).entries()) {
|
|
8615
|
+
const found = regex ? regex.exec(line) : plainMatch(line, needle, options.caseSensitive);
|
|
8616
|
+
if (found === null) {
|
|
8617
|
+
continue;
|
|
8618
|
+
}
|
|
8619
|
+
if (result.summary.total_matches >= options.maxResults) {
|
|
8620
|
+
result.summary.truncated = true;
|
|
8621
|
+
break;
|
|
8622
|
+
}
|
|
8623
|
+
matches.push({ line: index + 1, content: line.trim(), match: found[0] });
|
|
8624
|
+
result.summary.total_matches += 1;
|
|
8625
|
+
}
|
|
8626
|
+
if (matches.length > 0) {
|
|
8627
|
+
const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
|
|
8628
|
+
result.results.push({ file: `res://${relativePath}`, matches });
|
|
8629
|
+
result.summary.files_with_matches += 1;
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
};
|
|
8633
|
+
visit(projectPath);
|
|
8634
|
+
return notSearched.length === 0 ? result : { ...result, not_searched: notSearched };
|
|
8635
|
+
}
|
|
8636
|
+
var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, SIDECAR_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
|
|
8637
|
+
var init_project_scan = __esm(() => {
|
|
8638
|
+
init_errors();
|
|
8639
|
+
SKIPPED = new Set(["node_modules"]);
|
|
8640
|
+
SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
8641
|
+
SIDECAR_EXTENSIONS = new Set(["import", "uid"]);
|
|
8642
|
+
ASSET_EXTENSIONS = new Set([
|
|
8643
|
+
"png",
|
|
8644
|
+
"jpg",
|
|
8645
|
+
"jpeg",
|
|
8646
|
+
"webp",
|
|
8647
|
+
"svg",
|
|
8648
|
+
"bmp",
|
|
8649
|
+
"tga",
|
|
8650
|
+
"exr",
|
|
8651
|
+
"hdr",
|
|
8652
|
+
"ktx",
|
|
8653
|
+
"dds",
|
|
8654
|
+
"ttf",
|
|
8655
|
+
"otf",
|
|
8656
|
+
"woff",
|
|
8657
|
+
"woff2",
|
|
8658
|
+
"fnt",
|
|
8659
|
+
"wav",
|
|
8660
|
+
"mp3",
|
|
8661
|
+
"ogg",
|
|
8662
|
+
"glb",
|
|
8663
|
+
"gltf",
|
|
8664
|
+
"obj",
|
|
8665
|
+
"fbx",
|
|
8666
|
+
"blend",
|
|
8667
|
+
"dae",
|
|
8668
|
+
"ogv",
|
|
8669
|
+
"gdshader",
|
|
8670
|
+
"shader"
|
|
8671
|
+
]);
|
|
8672
|
+
CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
8673
|
+
BINARY_EXTENSIONS = new Set([
|
|
8674
|
+
...[...ASSET_EXTENSIONS].filter((extension) => !["svg", "gltf", "gdshader", "shader", "fnt"].includes(extension)),
|
|
8675
|
+
"scn",
|
|
8676
|
+
"res",
|
|
8677
|
+
"zip",
|
|
8678
|
+
"pck",
|
|
8679
|
+
"exe",
|
|
8680
|
+
"dll",
|
|
8681
|
+
"so",
|
|
8682
|
+
"dylib"
|
|
8683
|
+
]);
|
|
8684
|
+
});
|
|
8685
|
+
|
|
8686
|
+
// src/import-freshness.ts
|
|
8687
|
+
import { createHash } from "node:crypto";
|
|
8688
|
+
import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync4, statSync as statSync5 } from "node:fs";
|
|
8689
|
+
import { join as join8 } from "node:path";
|
|
8690
|
+
function md5Of(file) {
|
|
8691
|
+
let stats;
|
|
8692
|
+
try {
|
|
8693
|
+
stats = statSync5(file);
|
|
8694
|
+
} catch {
|
|
8695
|
+
return null;
|
|
8696
|
+
}
|
|
8697
|
+
const known = hashes.get(file);
|
|
8698
|
+
if (known !== undefined && known.size === stats.size && known.mtimeMs === stats.mtimeMs) {
|
|
8699
|
+
return known.md5;
|
|
8700
|
+
}
|
|
8701
|
+
let md5;
|
|
8702
|
+
try {
|
|
8703
|
+
md5 = createHash("md5").update(readFileSync4(file)).digest("hex");
|
|
8704
|
+
} catch {
|
|
8705
|
+
return null;
|
|
8706
|
+
}
|
|
8707
|
+
hashes.set(file, { size: stats.size, mtimeMs: stats.mtimeMs, md5 });
|
|
8708
|
+
return md5;
|
|
8709
|
+
}
|
|
8710
|
+
function sectionOf(text, section) {
|
|
8711
|
+
const keys = new Map;
|
|
8712
|
+
let inside = false;
|
|
8713
|
+
for (const line of text.split(/\r?\n/)) {
|
|
8714
|
+
const header = /^\[([^\]]+)\]\s*$/.exec(line);
|
|
8715
|
+
if (header !== null) {
|
|
8716
|
+
inside = header[1] === section;
|
|
8717
|
+
continue;
|
|
8718
|
+
}
|
|
8719
|
+
const pair = inside ? /^([A-Za-z0-9_./]+)=(.*)$/.exec(line) : null;
|
|
8720
|
+
if (pair?.[1] !== undefined && pair[2] !== undefined) {
|
|
8721
|
+
keys.set(pair[1], pair[2]);
|
|
8722
|
+
}
|
|
8723
|
+
}
|
|
8724
|
+
return keys;
|
|
8725
|
+
}
|
|
8726
|
+
function unquoted(value) {
|
|
8727
|
+
return value === undefined ? "" : value.replace(/^"(.*)"$/, "$1");
|
|
8728
|
+
}
|
|
8729
|
+
function listed(value) {
|
|
8730
|
+
return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
|
|
8731
|
+
}
|
|
8732
|
+
function onDisk(projectPath, resPath) {
|
|
8733
|
+
return join8(projectPath, resPath.slice("res://".length));
|
|
8734
|
+
}
|
|
8735
|
+
function behind(projectPath, resPath, sidecar) {
|
|
8736
|
+
const remap = sectionOf(sidecar, "remap");
|
|
8737
|
+
const importer = unquoted(remap.get("importer"));
|
|
8738
|
+
if (importer === "keep" || importer === "skip") {
|
|
8739
|
+
return null;
|
|
8740
|
+
}
|
|
8741
|
+
const outputs = listed(sectionOf(sidecar, "deps").get("dest_files"));
|
|
8742
|
+
if (outputs.some((output) => !existsSync7(onDisk(projectPath, output)))) {
|
|
8743
|
+
return "its imported output is not on disk";
|
|
8744
|
+
}
|
|
8745
|
+
const name = resPath.slice(resPath.lastIndexOf("/") + 1);
|
|
8746
|
+
const record = join8(projectPath, ".godot", "imported", `${name}-${createHash("md5").update(resPath, "utf8").digest("hex")}.md5`);
|
|
8747
|
+
let recorded = "";
|
|
8748
|
+
try {
|
|
8749
|
+
recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync4(record, "utf8"))?.[1] ?? "";
|
|
8750
|
+
} catch {
|
|
8751
|
+
recorded = "";
|
|
8752
|
+
}
|
|
8753
|
+
if (recorded === "") {
|
|
8754
|
+
return "its import recorded no hash of its source";
|
|
8755
|
+
}
|
|
8756
|
+
const current = md5Of(onDisk(projectPath, resPath));
|
|
8757
|
+
return current === null || current === recorded ? null : "the source changed after it was imported";
|
|
8758
|
+
}
|
|
8759
|
+
function staleImports(projectPath) {
|
|
8760
|
+
const stale = [];
|
|
8761
|
+
const visit = (directory, resDirectory) => {
|
|
8762
|
+
if (existsSync7(join8(directory, ".gdignore")) || existsSync7(join8(directory, VERSION_MARKER))) {
|
|
8763
|
+
return;
|
|
8764
|
+
}
|
|
8765
|
+
let entries;
|
|
8766
|
+
try {
|
|
8767
|
+
entries = readdirSync5(directory, { withFileTypes: true });
|
|
8768
|
+
} catch {
|
|
8769
|
+
return;
|
|
8770
|
+
}
|
|
8771
|
+
const names = new Set(entries.map((entry) => entry.name));
|
|
8772
|
+
for (const entry of entries) {
|
|
8773
|
+
if (entry.name.startsWith(".")) {
|
|
8774
|
+
continue;
|
|
8775
|
+
}
|
|
8776
|
+
const resPath = `${resDirectory}${entry.name}`;
|
|
8777
|
+
if (entry.isDirectory()) {
|
|
8778
|
+
visit(join8(directory, entry.name), `${resPath}/`);
|
|
8779
|
+
continue;
|
|
8780
|
+
}
|
|
8781
|
+
if (!entry.isFile()) {
|
|
8782
|
+
continue;
|
|
8783
|
+
}
|
|
8784
|
+
if (entry.name.endsWith(".import")) {
|
|
8785
|
+
const source = resPath.slice(0, -".import".length);
|
|
8786
|
+
if (!names.has(entry.name.slice(0, -".import".length))) {
|
|
8787
|
+
continue;
|
|
8788
|
+
}
|
|
8789
|
+
let sidecar;
|
|
8790
|
+
try {
|
|
8791
|
+
sidecar = readFileSync4(join8(directory, entry.name), "utf8");
|
|
8792
|
+
} catch {
|
|
8793
|
+
continue;
|
|
8794
|
+
}
|
|
8795
|
+
const reason = behind(projectPath, source, sidecar);
|
|
8796
|
+
if (reason !== null) {
|
|
8797
|
+
stale.push({ path: source, reason });
|
|
8798
|
+
}
|
|
8799
|
+
continue;
|
|
8800
|
+
}
|
|
8801
|
+
const extension = entry.name.slice(entry.name.lastIndexOf(".") + 1).toLowerCase();
|
|
8802
|
+
if (IMPORTABLE.has(extension) && !names.has(`${entry.name}.import`)) {
|
|
8803
|
+
stale.push({ path: resPath, reason: "it has not been imported" });
|
|
8804
|
+
}
|
|
8805
|
+
}
|
|
8806
|
+
};
|
|
8807
|
+
visit(projectPath, "res://");
|
|
8808
|
+
return stale.sort((one, other) => one.path.localeCompare(other.path));
|
|
8809
|
+
}
|
|
8810
|
+
var IMPORTABLE_EXTENSIONS, IMPORTABLE, hashes;
|
|
8811
|
+
var init_import_freshness = __esm(() => {
|
|
8812
|
+
init_project_scan();
|
|
8813
|
+
IMPORTABLE_EXTENSIONS = [
|
|
8814
|
+
"png",
|
|
8815
|
+
"jpg",
|
|
8816
|
+
"jpeg",
|
|
8817
|
+
"webp",
|
|
8818
|
+
"svg",
|
|
8819
|
+
"bmp",
|
|
8820
|
+
"tga",
|
|
8821
|
+
"exr",
|
|
8822
|
+
"hdr",
|
|
8823
|
+
"dds",
|
|
8824
|
+
"ktx",
|
|
8825
|
+
"wav",
|
|
8826
|
+
"mp3",
|
|
8827
|
+
"ogg",
|
|
8828
|
+
"ttf",
|
|
8829
|
+
"otf",
|
|
8830
|
+
"ttc",
|
|
8831
|
+
"otc",
|
|
8832
|
+
"woff",
|
|
8833
|
+
"woff2",
|
|
8834
|
+
"pfb",
|
|
8835
|
+
"pfm",
|
|
8836
|
+
"fnt",
|
|
8837
|
+
"font",
|
|
8838
|
+
"glb",
|
|
8839
|
+
"gltf",
|
|
8840
|
+
"fbx",
|
|
8841
|
+
"obj",
|
|
8842
|
+
"dae"
|
|
8843
|
+
];
|
|
8844
|
+
IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
|
|
8845
|
+
hashes = new Map;
|
|
8846
|
+
});
|
|
8847
|
+
|
|
8370
8848
|
// src/library-copies.ts
|
|
8371
|
-
import { existsSync as
|
|
8372
|
-
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute3, join as
|
|
8849
|
+
import { existsSync as existsSync8, readdirSync as readdirSync6, readFileSync as readFileSync5, renameSync, unlinkSync } from "node:fs";
|
|
8850
|
+
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute3, join as join9 } from "node:path";
|
|
8373
8851
|
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
8374
8852
|
const setAside = [];
|
|
8375
8853
|
const swept = [];
|
|
@@ -8379,24 +8857,24 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
8379
8857
|
for (const library of windowsLibraries(projectPath)) {
|
|
8380
8858
|
const folder = dirname6(library);
|
|
8381
8859
|
const copy = `~${basename5(library)}`;
|
|
8382
|
-
if (
|
|
8383
|
-
const aside =
|
|
8860
|
+
if (existsSync8(join9(folder, copy))) {
|
|
8861
|
+
const aside = join9(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
8384
8862
|
try {
|
|
8385
|
-
renameSync(
|
|
8863
|
+
renameSync(join9(folder, copy), aside);
|
|
8386
8864
|
setAside.push(aside);
|
|
8387
8865
|
} catch {}
|
|
8388
8866
|
}
|
|
8389
8867
|
let names;
|
|
8390
8868
|
try {
|
|
8391
|
-
names =
|
|
8869
|
+
names = readdirSync6(folder);
|
|
8392
8870
|
} catch {
|
|
8393
8871
|
continue;
|
|
8394
8872
|
}
|
|
8395
8873
|
for (const name of names) {
|
|
8396
8874
|
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
8397
8875
|
try {
|
|
8398
|
-
unlinkSync(
|
|
8399
|
-
swept.push(
|
|
8876
|
+
unlinkSync(join9(folder, name));
|
|
8877
|
+
swept.push(join9(folder, name));
|
|
8400
8878
|
} catch {}
|
|
8401
8879
|
}
|
|
8402
8880
|
}
|
|
@@ -8404,7 +8882,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
8404
8882
|
return { setAside, swept };
|
|
8405
8883
|
}
|
|
8406
8884
|
function windowsLibraries(projectPath) {
|
|
8407
|
-
const listed = readOrNull(
|
|
8885
|
+
const listed = readOrNull(join9(projectPath, ".godot", "extension_list.cfg"));
|
|
8408
8886
|
if (listed === null) {
|
|
8409
8887
|
return [];
|
|
8410
8888
|
}
|
|
@@ -8436,13 +8914,13 @@ function resolveResource(projectPath, relativeTo, path) {
|
|
|
8436
8914
|
return null;
|
|
8437
8915
|
}
|
|
8438
8916
|
if (path.startsWith("res://")) {
|
|
8439
|
-
return
|
|
8917
|
+
return join9(projectPath, path.slice("res://".length));
|
|
8440
8918
|
}
|
|
8441
|
-
return isAbsolute3(path) ? path :
|
|
8919
|
+
return isAbsolute3(path) ? path : join9(relativeTo, path);
|
|
8442
8920
|
}
|
|
8443
8921
|
function readOrNull(path) {
|
|
8444
8922
|
try {
|
|
8445
|
-
return
|
|
8923
|
+
return readFileSync5(path, "utf8");
|
|
8446
8924
|
} catch {
|
|
8447
8925
|
return null;
|
|
8448
8926
|
}
|
|
@@ -8450,23 +8928,23 @@ function readOrNull(path) {
|
|
|
8450
8928
|
var init_library_copies = () => {};
|
|
8451
8929
|
|
|
8452
8930
|
// src/reimport.ts
|
|
8453
|
-
import { createHash } from "node:crypto";
|
|
8454
|
-
import { existsSync as
|
|
8455
|
-
import { basename as basename6, join as
|
|
8931
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
8932
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync6, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8933
|
+
import { basename as basename6, join as join10 } from "node:path";
|
|
8456
8934
|
function forceNextImport(projectPath, resourcePath) {
|
|
8457
8935
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
8458
|
-
const sidecar =
|
|
8459
|
-
if (!
|
|
8936
|
+
const sidecar = join10(projectPath, `${relative}.import`);
|
|
8937
|
+
if (!existsSync9(sidecar)) {
|
|
8460
8938
|
return;
|
|
8461
8939
|
}
|
|
8462
|
-
const { atime, mtimeMs } =
|
|
8463
|
-
writeFileSync3(sidecar,
|
|
8940
|
+
const { atime, mtimeMs } = statSync6(sidecar);
|
|
8941
|
+
writeFileSync3(sidecar, readFileSync6(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
8464
8942
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
8465
8943
|
rmSync3(importRecord(projectPath, resourcePath), { force: true });
|
|
8466
8944
|
}
|
|
8467
8945
|
function importRecord(projectPath, resourcePath) {
|
|
8468
|
-
const digest =
|
|
8469
|
-
return
|
|
8946
|
+
const digest = createHash2("md5").update(resourcePath).digest("hex");
|
|
8947
|
+
return join10(projectPath, ".godot", "imported", `${basename6(resourcePath)}-${digest}.md5`);
|
|
8470
8948
|
}
|
|
8471
8949
|
var init_reimport = () => {};
|
|
8472
8950
|
|
|
@@ -8496,17 +8974,17 @@ function alive(pid) {
|
|
|
8496
8974
|
var init_alive = () => {};
|
|
8497
8975
|
|
|
8498
8976
|
// src/scratch.ts
|
|
8499
|
-
import { existsSync as
|
|
8977
|
+
import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync7, rmSync as rmSync4 } from "node:fs";
|
|
8500
8978
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8501
|
-
import { join as
|
|
8979
|
+
import { join as join11 } from "node:path";
|
|
8502
8980
|
import process8 from "node:process";
|
|
8503
8981
|
function scratchDirectory(kind) {
|
|
8504
|
-
return mkdtempSync(
|
|
8982
|
+
return mkdtempSync(join11(tmpdir2(), `gdharness-${kind}-${process8.pid}-`));
|
|
8505
8983
|
}
|
|
8506
8984
|
function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
8507
8985
|
let entries;
|
|
8508
8986
|
try {
|
|
8509
|
-
entries =
|
|
8987
|
+
entries = readdirSync7(directory);
|
|
8510
8988
|
} catch {
|
|
8511
8989
|
return;
|
|
8512
8990
|
}
|
|
@@ -8517,7 +8995,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
|
|
|
8517
8995
|
continue;
|
|
8518
8996
|
}
|
|
8519
8997
|
try {
|
|
8520
|
-
remove(
|
|
8998
|
+
remove(join11(directory, entry));
|
|
8521
8999
|
} catch {}
|
|
8522
9000
|
}
|
|
8523
9001
|
}
|
|
@@ -8555,7 +9033,7 @@ function tryAgain(path, attempt) {
|
|
|
8555
9033
|
lastTryOnExit();
|
|
8556
9034
|
}
|
|
8557
9035
|
const after = AGAIN_AFTER_MS[attempt];
|
|
8558
|
-
if (after === undefined || !
|
|
9036
|
+
if (after === undefined || !existsSync10(path)) {
|
|
8559
9037
|
waiting.delete(path);
|
|
8560
9038
|
if (after === undefined) {
|
|
8561
9039
|
atExit.add(path);
|
|
@@ -8744,6 +9222,29 @@ function ignoredYetRan(report, ignored) {
|
|
|
8744
9222
|
}
|
|
8745
9223
|
return found;
|
|
8746
9224
|
}
|
|
9225
|
+
function ignoredLeftOut(report, ignored, declared) {
|
|
9226
|
+
const nameOf = (path) => (path.split("/").at(-1) ?? path).replace(/\.gd$/, "");
|
|
9227
|
+
const leftOut = new Set;
|
|
9228
|
+
const unmatched = [];
|
|
9229
|
+
for (const entry of ignored) {
|
|
9230
|
+
const bare = entry.replace("res://", "");
|
|
9231
|
+
const at = bare.lastIndexOf(":");
|
|
9232
|
+
const suiteNamed = at === -1 ? bare : bare.slice(0, at);
|
|
9233
|
+
const testNamed = at === -1 ? null : bare.slice(at + 1);
|
|
9234
|
+
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 })));
|
|
9235
|
+
if (named.length === 0) {
|
|
9236
|
+
unmatched.push(entry);
|
|
9237
|
+
continue;
|
|
9238
|
+
}
|
|
9239
|
+
for (const { suite, test } of named) {
|
|
9240
|
+
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)));
|
|
9241
|
+
if (!reported) {
|
|
9242
|
+
leftOut.add(`${nameOf(suite.path)}:${test}`);
|
|
9243
|
+
}
|
|
9244
|
+
}
|
|
9245
|
+
}
|
|
9246
|
+
return { leftOut: [...leftOut].sort(), unmatched };
|
|
9247
|
+
}
|
|
8747
9248
|
function caseOf(suite, element) {
|
|
8748
9249
|
const of = (kind) => element.children.filter((child) => child.name === kind).map((child) => {
|
|
8749
9250
|
const detail = child.text.trim();
|
|
@@ -9104,7 +9605,7 @@ var init_junit = __esm(() => {
|
|
|
9104
9605
|
});
|
|
9105
9606
|
|
|
9106
9607
|
// src/script-origins.ts
|
|
9107
|
-
import { join as
|
|
9608
|
+
import { join as join12 } from "node:path";
|
|
9108
9609
|
function unresolvedClassIn(message) {
|
|
9109
9610
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
9110
9611
|
}
|
|
@@ -9166,7 +9667,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
9166
9667
|
const ran = await runEngine(godotPath, [
|
|
9167
9668
|
"--headless",
|
|
9168
9669
|
"--log-file",
|
|
9169
|
-
|
|
9670
|
+
join12(logDir, "engine.log"),
|
|
9170
9671
|
"--path",
|
|
9171
9672
|
projectPath,
|
|
9172
9673
|
"--check-only",
|
|
@@ -9212,8 +9713,8 @@ var init_script_origins = __esm(() => {
|
|
|
9212
9713
|
});
|
|
9213
9714
|
|
|
9214
9715
|
// src/headless.ts
|
|
9215
|
-
import { existsSync as
|
|
9216
|
-
import { dirname as dirname7, join as
|
|
9716
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9717
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
9217
9718
|
function snakeCased(params) {
|
|
9218
9719
|
const result = emptyRecord();
|
|
9219
9720
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -9225,8 +9726,10 @@ function snakeCased(params) {
|
|
|
9225
9726
|
function engineExtras(outcome) {
|
|
9226
9727
|
const after = outcome.afterAnswerMessages ?? [];
|
|
9227
9728
|
const before = outcome.beforeOperationMessages ?? [];
|
|
9729
|
+
const notImported = notImportedNote(outcome.messages);
|
|
9228
9730
|
return {
|
|
9229
9731
|
...outcome.messages.length > 0 ? { engine_messages: outcome.messages } : {},
|
|
9732
|
+
...notImported === undefined ? {} : { engine_messages_note: notImported },
|
|
9230
9733
|
...before.length > 0 ? {
|
|
9231
9734
|
engine_messages_before_operation: before,
|
|
9232
9735
|
engine_messages_before_operation_note: withNotCompiled(BEFORE_OPERATION_NOTE, before)
|
|
@@ -9245,6 +9748,20 @@ function withNotCompiled(note, entries) {
|
|
|
9245
9748
|
const path = entries.map(notCompiledIn).find((found) => found !== undefined);
|
|
9246
9749
|
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
9750
|
}
|
|
9751
|
+
function notImportedNote(entries) {
|
|
9752
|
+
const paths = [
|
|
9753
|
+
...new Set(entries.flatMap((entry) => {
|
|
9754
|
+
const path = /^No loader found for resource: (res:\/\/\S+)/.exec(entry.text)?.[1];
|
|
9755
|
+
const extension = path?.split(".").pop()?.toLowerCase() ?? "";
|
|
9756
|
+
return path !== undefined && IMPORTABLE_EXTENSIONS.includes(extension) ? [path] : [];
|
|
9757
|
+
}))
|
|
9758
|
+
];
|
|
9759
|
+
if (paths.length === 0) {
|
|
9760
|
+
return;
|
|
9761
|
+
}
|
|
9762
|
+
const one = paths.length === 1;
|
|
9763
|
+
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"}.`;
|
|
9764
|
+
}
|
|
9248
9765
|
function withoutAsides(entries) {
|
|
9249
9766
|
const kept = [];
|
|
9250
9767
|
let aside = false;
|
|
@@ -9351,7 +9868,7 @@ async function importOnce(godotPath, projectPath) {
|
|
|
9351
9868
|
ran = await runEngine(godotPath, [
|
|
9352
9869
|
"--headless",
|
|
9353
9870
|
"--log-file",
|
|
9354
|
-
|
|
9871
|
+
join13(logDir, "engine.log"),
|
|
9355
9872
|
"--path",
|
|
9356
9873
|
projectPath,
|
|
9357
9874
|
"--import"
|
|
@@ -9373,13 +9890,13 @@ async function importOnce(godotPath, projectPath) {
|
|
|
9373
9890
|
}
|
|
9374
9891
|
async function runOperation(engine, operation, params, projectPath) {
|
|
9375
9892
|
const paramsDir = scratchDirectory("params");
|
|
9376
|
-
const paramsFile =
|
|
9377
|
-
const answerFile =
|
|
9893
|
+
const paramsFile = join13(paramsDir, `${operation}.json`);
|
|
9894
|
+
const answerFile = join13(paramsDir, "answer.json");
|
|
9378
9895
|
writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
9379
9896
|
const args = [
|
|
9380
9897
|
"--headless",
|
|
9381
9898
|
"--log-file",
|
|
9382
|
-
|
|
9899
|
+
join13(paramsDir, "engine.log"),
|
|
9383
9900
|
"--path",
|
|
9384
9901
|
projectPath,
|
|
9385
9902
|
"--script",
|
|
@@ -9467,10 +9984,10 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
9467
9984
|
};
|
|
9468
9985
|
}
|
|
9469
9986
|
function answerIn(file) {
|
|
9470
|
-
if (!
|
|
9987
|
+
if (!existsSync11(file)) {
|
|
9471
9988
|
return { kind: "none" };
|
|
9472
9989
|
}
|
|
9473
|
-
const text =
|
|
9990
|
+
const text = readFileSync7(file, "utf8");
|
|
9474
9991
|
try {
|
|
9475
9992
|
const parsed = JSON.parse(text);
|
|
9476
9993
|
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 +10002,7 @@ var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, wh
|
|
|
9485
10002
|
var init_headless = __esm(() => {
|
|
9486
10003
|
init_doc_cache();
|
|
9487
10004
|
init_engine_run();
|
|
10005
|
+
init_import_freshness();
|
|
9488
10006
|
init_library_copies();
|
|
9489
10007
|
init_paths();
|
|
9490
10008
|
init_reimport();
|
|
@@ -9493,9 +10011,9 @@ var init_headless = __esm(() => {
|
|
|
9493
10011
|
});
|
|
9494
10012
|
|
|
9495
10013
|
// src/update-check.ts
|
|
9496
|
-
import { existsSync as
|
|
10014
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9497
10015
|
import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
|
|
9498
|
-
import { join as
|
|
10016
|
+
import { join as join14 } from "node:path";
|
|
9499
10017
|
function packageUrl(root) {
|
|
9500
10018
|
return `${root.replace(/\/+$/, "")}/gdharness/latest`;
|
|
9501
10019
|
}
|
|
@@ -9517,28 +10035,28 @@ function cacheDirectory(environment) {
|
|
|
9517
10035
|
};
|
|
9518
10036
|
const home = set("HOME") ?? homedir4();
|
|
9519
10037
|
if (process.platform === "win32") {
|
|
9520
|
-
return
|
|
10038
|
+
return join14(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
9521
10039
|
}
|
|
9522
10040
|
if (process.platform === "darwin") {
|
|
9523
|
-
return
|
|
10041
|
+
return join14(home, "Library", "Caches", "gdharness");
|
|
9524
10042
|
}
|
|
9525
|
-
return
|
|
10043
|
+
return join14(set("XDG_CACHE_HOME") ?? join14(home, ".cache"), "gdharness");
|
|
9526
10044
|
}
|
|
9527
10045
|
function cacheFile(environment = process.env) {
|
|
9528
10046
|
try {
|
|
9529
10047
|
const directory = cacheDirectory(environment);
|
|
9530
10048
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
9531
|
-
return
|
|
10049
|
+
return join14(directory, "update-check.json");
|
|
9532
10050
|
} catch {
|
|
9533
|
-
return
|
|
10051
|
+
return join14(tmpdir3(), "gdharness-update-check.json");
|
|
9534
10052
|
}
|
|
9535
10053
|
}
|
|
9536
10054
|
function readCache(path) {
|
|
9537
10055
|
try {
|
|
9538
|
-
if (!
|
|
10056
|
+
if (!existsSync12(path)) {
|
|
9539
10057
|
return null;
|
|
9540
10058
|
}
|
|
9541
|
-
const parsed = JSON.parse(
|
|
10059
|
+
const parsed = JSON.parse(readFileSync8(path, "utf8"));
|
|
9542
10060
|
if (typeof parsed !== "object" || parsed === null) {
|
|
9543
10061
|
return null;
|
|
9544
10062
|
}
|
|
@@ -9686,7 +10204,7 @@ var init_update_check = __esm(() => {
|
|
|
9686
10204
|
});
|
|
9687
10205
|
|
|
9688
10206
|
// src/server-version.ts
|
|
9689
|
-
import { readFileSync as
|
|
10207
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
9690
10208
|
function staleAddon(addonDigest, shippedDigest) {
|
|
9691
10209
|
return addonDigest !== shippedDigest;
|
|
9692
10210
|
}
|
|
@@ -9745,7 +10263,7 @@ var init_server_version = __esm(() => {
|
|
|
9745
10263
|
DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
|
|
9746
10264
|
SERVER_VERSION = (() => {
|
|
9747
10265
|
try {
|
|
9748
|
-
const pkg = JSON.parse(
|
|
10266
|
+
const pkg = JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8"));
|
|
9749
10267
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
9750
10268
|
} catch {
|
|
9751
10269
|
return "0.0.0";
|
|
@@ -9754,7 +10272,7 @@ var init_server_version = __esm(() => {
|
|
|
9754
10272
|
});
|
|
9755
10273
|
|
|
9756
10274
|
// src/issues.ts
|
|
9757
|
-
import { createHash as
|
|
10275
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9758
10276
|
import process9 from "node:process";
|
|
9759
10277
|
function feedbackNotice(every) {
|
|
9760
10278
|
return JSON.stringify({
|
|
@@ -9773,7 +10291,7 @@ function runtime() {
|
|
|
9773
10291
|
function defectSignature(where, message) {
|
|
9774
10292
|
const normalised = `${where}
|
|
9775
10293
|
${message}`.toLowerCase().replaceAll("\\", "/").replaceAll(/(?:[a-z]:)?(?:\/+[\w.-]+){2,}/gu, "<path>").replaceAll(/\d+/gu, "0");
|
|
9776
|
-
return
|
|
10294
|
+
return createHash3("sha256").update(normalised).digest("hex").slice(0, 8);
|
|
9777
10295
|
}
|
|
9778
10296
|
function facts(where, message, godotVersion) {
|
|
9779
10297
|
return [
|
|
@@ -10530,8 +11048,8 @@ var init_gdscript_source = __esm(() => {
|
|
|
10530
11048
|
});
|
|
10531
11049
|
|
|
10532
11050
|
// src/class-cache.ts
|
|
10533
|
-
import { existsSync as
|
|
10534
|
-
import { join as
|
|
11051
|
+
import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync10, statSync as statSync7 } from "node:fs";
|
|
11052
|
+
import { join as join15, posix } from "node:path";
|
|
10535
11053
|
function isToolScript(source) {
|
|
10536
11054
|
return TOOL.test(codeOf(source));
|
|
10537
11055
|
}
|
|
@@ -10550,18 +11068,18 @@ function declaredClassOf(source) {
|
|
|
10550
11068
|
}
|
|
10551
11069
|
function eachScript(projectPath, each) {
|
|
10552
11070
|
const visit = (directory, prefix) => {
|
|
10553
|
-
if (
|
|
11071
|
+
if (existsSync13(join15(directory, ".gdignore"))) {
|
|
10554
11072
|
return;
|
|
10555
11073
|
}
|
|
10556
|
-
for (const entry of
|
|
11074
|
+
for (const entry of readdirSync8(directory, { withFileTypes: true })) {
|
|
10557
11075
|
if (entry.name.startsWith(".")) {
|
|
10558
11076
|
continue;
|
|
10559
11077
|
}
|
|
10560
|
-
const path =
|
|
11078
|
+
const path = join15(directory, entry.name);
|
|
10561
11079
|
if (entry.isDirectory()) {
|
|
10562
11080
|
visit(path, `${prefix}${entry.name}/`);
|
|
10563
11081
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
10564
|
-
each(`res://${prefix}${entry.name}`,
|
|
11082
|
+
each(`res://${prefix}${entry.name}`, readFileSync10(path, "utf8"));
|
|
10565
11083
|
}
|
|
10566
11084
|
}
|
|
10567
11085
|
};
|
|
@@ -10583,12 +11101,12 @@ function scriptsNaming(projectPath, classes) {
|
|
|
10583
11101
|
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
10584
11102
|
}
|
|
10585
11103
|
function cachedClasses(projectPath) {
|
|
10586
|
-
const cache =
|
|
10587
|
-
if (!
|
|
11104
|
+
const cache = join15(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
11105
|
+
if (!existsSync13(cache)) {
|
|
10588
11106
|
return null;
|
|
10589
11107
|
}
|
|
10590
11108
|
const listed = new Map;
|
|
10591
|
-
const text =
|
|
11109
|
+
const text = readFileSync10(cache, "utf8");
|
|
10592
11110
|
for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
|
|
10593
11111
|
listed.set(entry[1] ?? "", entry[2] ?? "");
|
|
10594
11112
|
}
|
|
@@ -10881,14 +11399,14 @@ function uncachedForTheEngineNote(projectPath, types) {
|
|
|
10881
11399
|
}
|
|
10882
11400
|
const one = types.length === 1;
|
|
10883
11401
|
const named = types.map((type) => `${type.type} (${type.declaredIn})`).join(", ");
|
|
10884
|
-
const cacheExists =
|
|
11402
|
+
const cacheExists = existsSync13(join15(projectPath, ".godot", "global_script_class_cache.cfg"));
|
|
10885
11403
|
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
11404
|
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
11405
|
}
|
|
10888
11406
|
function cacheWrittenAt(projectPath) {
|
|
10889
|
-
const cache =
|
|
11407
|
+
const cache = join15(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
10890
11408
|
try {
|
|
10891
|
-
return
|
|
11409
|
+
return statSync7(cache).mtimeMs;
|
|
10892
11410
|
} catch {
|
|
10893
11411
|
return null;
|
|
10894
11412
|
}
|
|
@@ -10900,14 +11418,14 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
10900
11418
|
return false;
|
|
10901
11419
|
}
|
|
10902
11420
|
try {
|
|
10903
|
-
return
|
|
11421
|
+
return statSync7(join15(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
10904
11422
|
} catch {
|
|
10905
11423
|
return false;
|
|
10906
11424
|
}
|
|
10907
11425
|
});
|
|
10908
11426
|
}
|
|
10909
11427
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
10910
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !
|
|
11428
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync13(join15(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
10911
11429
|
}
|
|
10912
11430
|
function staleAgainst(cached, projectPath) {
|
|
10913
11431
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -10931,314 +11449,6 @@ var init_class_cache = __esm(() => {
|
|
|
10931
11449
|
TOOL = /^[ \t]*(?:@[a-z_]+(?:\([^)\n]*\))?[ \t]+)*@tool\b/m;
|
|
10932
11450
|
});
|
|
10933
11451
|
|
|
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
11452
|
// src/answer-json.ts
|
|
11243
11453
|
function answerJson(value) {
|
|
11244
11454
|
return JSON.stringify(value, null, 2).replace(SCALAR_ARRAY, (_whole, items) => `[${items.split(/,\n\s+/).join(", ")}]`);
|
|
@@ -11250,7 +11460,7 @@ var init_answer_json = __esm(() => {
|
|
|
11250
11460
|
});
|
|
11251
11461
|
|
|
11252
11462
|
// src/resources.ts
|
|
11253
|
-
import { readFileSync as
|
|
11463
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
11254
11464
|
import { extname as extname2, resolve as resolve3 } from "node:path";
|
|
11255
11465
|
function ensureProjectPath(getProjectPath) {
|
|
11256
11466
|
const projectPath = getProjectPath();
|
|
@@ -11446,7 +11656,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
11446
11656
|
const parsedUri = parseGodotUri(uri);
|
|
11447
11657
|
if (parsedUri.kind === "project-info") {
|
|
11448
11658
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
11449
|
-
const rawProject =
|
|
11659
|
+
const rawProject = readFileSync11(projectFilePath, "utf-8");
|
|
11450
11660
|
const parsedProject = parseProjectGodot(rawProject);
|
|
11451
11661
|
return {
|
|
11452
11662
|
mimeType: "application/json",
|
|
@@ -11455,7 +11665,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
11455
11665
|
}
|
|
11456
11666
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
11457
11667
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
11458
|
-
const text =
|
|
11668
|
+
const text = readFileSync11(filePath, "utf-8");
|
|
11459
11669
|
return {
|
|
11460
11670
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
11461
11671
|
text
|
|
@@ -11596,19 +11806,19 @@ function readParams(params, key) {
|
|
|
11596
11806
|
}
|
|
11597
11807
|
|
|
11598
11808
|
// src/setup.ts
|
|
11599
|
-
import { createHash as
|
|
11809
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
11600
11810
|
import {
|
|
11601
11811
|
cpSync,
|
|
11602
|
-
existsSync as
|
|
11812
|
+
existsSync as existsSync14,
|
|
11603
11813
|
mkdirSync as mkdirSync4,
|
|
11604
|
-
readdirSync as
|
|
11605
|
-
readFileSync as
|
|
11814
|
+
readdirSync as readdirSync9,
|
|
11815
|
+
readFileSync as readFileSync12,
|
|
11606
11816
|
rmdirSync,
|
|
11607
11817
|
rmSync as rmSync5,
|
|
11608
|
-
statSync as
|
|
11818
|
+
statSync as statSync8,
|
|
11609
11819
|
writeFileSync as writeFileSync6
|
|
11610
11820
|
} from "node:fs";
|
|
11611
|
-
import { dirname as dirname8, join as
|
|
11821
|
+
import { dirname as dirname8, join as join16 } from "node:path";
|
|
11612
11822
|
import { fileURLToPath } from "node:url";
|
|
11613
11823
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
11614
11824
|
return addonsDigest(EDITOR_PLUGINS, from);
|
|
@@ -11617,27 +11827,27 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
11617
11827
|
return addonsDigest([RUNTIME_ADDON], from);
|
|
11618
11828
|
}
|
|
11619
11829
|
function addonsDigest(names, from) {
|
|
11620
|
-
const hash =
|
|
11830
|
+
const hash = createHash4("sha256");
|
|
11621
11831
|
const walk = (directory, relative) => {
|
|
11622
|
-
for (const name of
|
|
11832
|
+
for (const name of readdirSync9(directory).sort()) {
|
|
11623
11833
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
11624
11834
|
continue;
|
|
11625
11835
|
}
|
|
11626
|
-
const path =
|
|
11836
|
+
const path = join16(directory, name);
|
|
11627
11837
|
const inside = `${relative}/${name}`;
|
|
11628
|
-
if (
|
|
11838
|
+
if (statSync8(path).isDirectory()) {
|
|
11629
11839
|
walk(path, inside);
|
|
11630
11840
|
} else {
|
|
11631
11841
|
hash.update(`${inside}
|
|
11632
11842
|
`);
|
|
11633
|
-
hash.update(
|
|
11843
|
+
hash.update(readFileSync12(path));
|
|
11634
11844
|
hash.update(`
|
|
11635
11845
|
`);
|
|
11636
11846
|
}
|
|
11637
11847
|
}
|
|
11638
11848
|
};
|
|
11639
11849
|
for (const name of names) {
|
|
11640
|
-
walk(
|
|
11850
|
+
walk(join16(from, name), name);
|
|
11641
11851
|
}
|
|
11642
11852
|
return hash.digest("hex");
|
|
11643
11853
|
}
|
|
@@ -11656,28 +11866,28 @@ function installedRuntimeDigest(projectPath) {
|
|
|
11656
11866
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
11657
11867
|
}
|
|
11658
11868
|
function digestMarker(projectPath, addon) {
|
|
11659
|
-
const marker =
|
|
11660
|
-
if (!
|
|
11869
|
+
const marker = join16(projectPath, "addons", addon, DIGEST_MARKER);
|
|
11870
|
+
if (!existsSync14(marker)) {
|
|
11661
11871
|
return null;
|
|
11662
11872
|
}
|
|
11663
|
-
const said =
|
|
11873
|
+
const said = readFileSync12(marker, "utf8").trim();
|
|
11664
11874
|
return said === "" ? null : said;
|
|
11665
11875
|
}
|
|
11666
11876
|
function shippedAddonsDirectory() {
|
|
11667
|
-
return
|
|
11877
|
+
return join16(dirname8(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
11668
11878
|
}
|
|
11669
11879
|
function shippedOperationsScript() {
|
|
11670
|
-
return
|
|
11880
|
+
return join16(dirname8(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
11671
11881
|
}
|
|
11672
11882
|
function shippedListedRunner() {
|
|
11673
|
-
return
|
|
11883
|
+
return join16(dirname8(fileURLToPath(import.meta.url)), "godot", "tests", "listed.gd");
|
|
11674
11884
|
}
|
|
11675
11885
|
function gdUnit4Unusable(projectPath) {
|
|
11676
11886
|
const marker = "addons/gdUnit4/plugin.cfg";
|
|
11677
|
-
if (!
|
|
11887
|
+
if (!existsSync14(join16(projectPath, ...marker.split("/")))) {
|
|
11678
11888
|
return { missing: true, reason: `gdUnit4 is not installed in this project: no ${marker}` };
|
|
11679
11889
|
}
|
|
11680
|
-
if (!
|
|
11890
|
+
if (!existsSync14(join16(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
|
|
11681
11891
|
return {
|
|
11682
11892
|
missing: false,
|
|
11683
11893
|
reason: `gdUnit4 in this project is older than 6.0, which the shipped runner needs: no ${GDUNIT4_CI_RUNNER}`
|
|
@@ -11688,44 +11898,44 @@ function gdUnit4Unusable(projectPath) {
|
|
|
11688
11898
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
11689
11899
|
const installed = [];
|
|
11690
11900
|
for (const name of ADDONS) {
|
|
11691
|
-
const source =
|
|
11692
|
-
if (!
|
|
11901
|
+
const source = join16(from, name);
|
|
11902
|
+
if (!existsSync14(join16(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
11693
11903
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
11694
11904
|
}
|
|
11695
|
-
const target =
|
|
11696
|
-
const replaced =
|
|
11905
|
+
const target = join16(projectPath, "addons", name);
|
|
11906
|
+
const replaced = existsSync14(target);
|
|
11697
11907
|
rmSync5(target, { recursive: true, force: true });
|
|
11698
11908
|
mkdirSync4(dirname8(target), { recursive: true });
|
|
11699
11909
|
cpSync(source, target, { recursive: true });
|
|
11700
|
-
writeFileSync6(
|
|
11910
|
+
writeFileSync6(join16(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
11701
11911
|
`);
|
|
11702
11912
|
installed.push({ name, path: target, replaced });
|
|
11703
11913
|
}
|
|
11704
|
-
writeFileSync6(
|
|
11914
|
+
writeFileSync6(join16(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
11705
11915
|
`);
|
|
11706
|
-
writeFileSync6(
|
|
11916
|
+
writeFileSync6(join16(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
|
|
11707
11917
|
`);
|
|
11708
11918
|
return installed;
|
|
11709
11919
|
}
|
|
11710
11920
|
function installedAddonVersion(projectPath) {
|
|
11711
|
-
const marker =
|
|
11712
|
-
if (!
|
|
11921
|
+
const marker = join16(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
11922
|
+
if (!existsSync14(marker)) {
|
|
11713
11923
|
return null;
|
|
11714
11924
|
}
|
|
11715
|
-
const said =
|
|
11925
|
+
const said = readFileSync12(marker, "utf8").trim();
|
|
11716
11926
|
return said === "" ? null : said;
|
|
11717
11927
|
}
|
|
11718
11928
|
function removeAddons(projectPath) {
|
|
11719
11929
|
const removed = [];
|
|
11720
11930
|
for (const name of ADDONS) {
|
|
11721
|
-
const target =
|
|
11722
|
-
if (
|
|
11931
|
+
const target = join16(projectPath, "addons", name);
|
|
11932
|
+
if (existsSync14(target)) {
|
|
11723
11933
|
rmSync5(target, { recursive: true, force: true });
|
|
11724
11934
|
removed.push(target);
|
|
11725
11935
|
}
|
|
11726
11936
|
}
|
|
11727
|
-
const addons =
|
|
11728
|
-
if (removed.length > 0 &&
|
|
11937
|
+
const addons = join16(projectPath, "addons");
|
|
11938
|
+
if (removed.length > 0 && existsSync14(addons) && readdirSync9(addons).length === 0) {
|
|
11729
11939
|
rmdirSync(addons);
|
|
11730
11940
|
removed.push(addons);
|
|
11731
11941
|
}
|
|
@@ -11759,7 +11969,7 @@ function loaderAmong(projectPath, named) {
|
|
|
11759
11969
|
}
|
|
11760
11970
|
let source = null;
|
|
11761
11971
|
try {
|
|
11762
|
-
source =
|
|
11972
|
+
source = readFileSync12(join16(projectPath, path), "utf8");
|
|
11763
11973
|
} catch {}
|
|
11764
11974
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
11765
11975
|
return { name: entry, path, exists: true };
|
|
@@ -11801,10 +12011,10 @@ function enabledPlugins(settings) {
|
|
|
11801
12011
|
function inspectProject(projectPath) {
|
|
11802
12012
|
const problems = [];
|
|
11803
12013
|
const addons = ADDONS.map((name) => {
|
|
11804
|
-
const directory =
|
|
11805
|
-
const marker =
|
|
11806
|
-
const installed =
|
|
11807
|
-
const version =
|
|
12014
|
+
const directory = join16(projectPath, "addons", name);
|
|
12015
|
+
const marker = join16(directory, VERSION_MARKER);
|
|
12016
|
+
const installed = existsSync14(directory);
|
|
12017
|
+
const version = existsSync14(marker) ? readFileSync12(marker, "utf8").trim() : null;
|
|
11808
12018
|
const current = version === SERVER_VERSION;
|
|
11809
12019
|
if (!installed) {
|
|
11810
12020
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -11813,7 +12023,7 @@ function inspectProject(projectPath) {
|
|
|
11813
12023
|
}
|
|
11814
12024
|
return { name, installed, version, current };
|
|
11815
12025
|
});
|
|
11816
|
-
const settings = parseProjectGodot(
|
|
12026
|
+
const settings = parseProjectGodot(readFileSync12(join16(projectPath, "project.godot"), "utf8"));
|
|
11817
12027
|
const pluginsEnabled = enabledPlugins(settings);
|
|
11818
12028
|
for (const name of EDITOR_PLUGINS) {
|
|
11819
12029
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -11829,7 +12039,7 @@ function inspectProject(projectPath) {
|
|
|
11829
12039
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
11830
12040
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
11831
12041
|
for (const [name, path] of named) {
|
|
11832
|
-
if (!
|
|
12042
|
+
if (!existsSync14(join16(projectPath, path))) {
|
|
11833
12043
|
if (!addonMissing(path)) {
|
|
11834
12044
|
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
12045
|
}
|
|
@@ -11982,6 +12192,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11982
12192
|
init_launch();
|
|
11983
12193
|
PROJECT_INFO_SECTIONS = [
|
|
11984
12194
|
"autoloads",
|
|
12195
|
+
"classes",
|
|
11985
12196
|
"input_actions",
|
|
11986
12197
|
"plugins",
|
|
11987
12198
|
"export_presets",
|
|
@@ -12149,13 +12360,13 @@ var init_tool_definitions = __esm(() => {
|
|
|
12149
12360
|
name: "project_info",
|
|
12150
12361
|
title: "Project info",
|
|
12151
12362
|
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.",
|
|
12363
|
+
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
12364
|
parameters: {
|
|
12154
12365
|
projectPath: PROJECT_PATH,
|
|
12155
12366
|
include: {
|
|
12156
12367
|
type: "array",
|
|
12157
12368
|
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."
|
|
12369
|
+
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
12370
|
},
|
|
12160
12371
|
preset: { type: "string", description: "For validation: the export preset to validate against." },
|
|
12161
12372
|
detail: {
|
|
@@ -12300,7 +12511,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12300
12511
|
name: "project_search",
|
|
12301
12512
|
title: "Search the project",
|
|
12302
12513
|
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.",
|
|
12514
|
+
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
12515
|
parameters: {
|
|
12305
12516
|
projectPath: PROJECT_PATH,
|
|
12306
12517
|
query: { type: "string", description: "The text or pattern to find." },
|
|
@@ -12326,7 +12537,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12326
12537
|
direction: {
|
|
12327
12538
|
type: "string",
|
|
12328
12539
|
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."
|
|
12540
|
+
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
12541
|
},
|
|
12331
12542
|
depth: { type: "number", description: "forward: how many levels to follow. Default unlimited." },
|
|
12332
12543
|
includeBuiltin: {
|
|
@@ -12356,7 +12567,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12356
12567
|
options: {
|
|
12357
12568
|
type: "object",
|
|
12358
12569
|
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.`,
|
|
12570
|
+
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
12571
|
additionalProperties: true
|
|
12361
12572
|
},
|
|
12362
12573
|
reimport: {
|
|
@@ -12414,7 +12625,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12414
12625
|
{
|
|
12415
12626
|
name: "project_export",
|
|
12416
12627
|
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.",
|
|
12628
|
+
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
12629
|
parameters: {
|
|
12419
12630
|
projectPath: PROJECT_PATH,
|
|
12420
12631
|
preset: {
|
|
@@ -12451,7 +12662,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12451
12662
|
title: "Run tests",
|
|
12452
12663
|
effect: "additive",
|
|
12453
12664
|
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 in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and 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.",
|
|
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,
|
|
12665
|
+
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. 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
12666
|
parameters: {
|
|
12456
12667
|
projectPath: PROJECT_PATH,
|
|
12457
12668
|
path: {
|
|
@@ -12462,7 +12673,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12462
12673
|
ignore: {
|
|
12463
12674
|
type: "array",
|
|
12464
12675
|
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.'
|
|
12676
|
+
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
12677
|
},
|
|
12467
12678
|
tests: {
|
|
12468
12679
|
type: "array",
|
|
@@ -12906,7 +13117,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12906
13117
|
operations: {
|
|
12907
13118
|
structure: {
|
|
12908
13119
|
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",
|
|
13120
|
+
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
13121
|
requires: []
|
|
12911
13122
|
},
|
|
12912
13123
|
symbols: {
|
|
@@ -13615,7 +13826,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13615
13826
|
name: "runtime_input",
|
|
13616
13827
|
title: "Send input to the game",
|
|
13617
13828
|
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. None of these is aimed at anything, so none says what the event reached: click on a nodePath or says answers what was under the pointer. 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.`,
|
|
13829
|
+
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. None of these is aimed at anything, so none says what the event reached: click on a nodePath or says answers what was under the pointer. 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
13830
|
parameters: {
|
|
13620
13831
|
projectPath: RUNNING_PROJECT_PATH,
|
|
13621
13832
|
pid: RUNNING_PID,
|
|
@@ -14051,9 +14262,9 @@ function whatItCanChange(tool) {
|
|
|
14051
14262
|
if (destroying.length === 0) {
|
|
14052
14263
|
return "Adds or starts things, and overwrites and removes nothing itself.";
|
|
14053
14264
|
}
|
|
14054
|
-
return `Can overwrite, remove or end what is there, through ${
|
|
14265
|
+
return `Can overwrite, remove or end what is there, through ${listed2(destroying)}.`;
|
|
14055
14266
|
}
|
|
14056
|
-
function
|
|
14267
|
+
function listed2(items) {
|
|
14057
14268
|
const last = items.at(-1) ?? "";
|
|
14058
14269
|
return items.length === 1 ? last : `${items.slice(0, -1).join(", ")} and ${last}`;
|
|
14059
14270
|
}
|
|
@@ -14111,30 +14322,30 @@ var init_tool_reference = __esm(() => {
|
|
|
14111
14322
|
});
|
|
14112
14323
|
|
|
14113
14324
|
// src/desktop.ts
|
|
14114
|
-
import { createHash as
|
|
14325
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
14115
14326
|
import {
|
|
14116
14327
|
closeSync,
|
|
14117
|
-
existsSync as
|
|
14328
|
+
existsSync as existsSync16,
|
|
14118
14329
|
mkdtempSync as mkdtempSync2,
|
|
14119
14330
|
openSync,
|
|
14120
|
-
readFileSync as
|
|
14331
|
+
readFileSync as readFileSync13,
|
|
14121
14332
|
rmSync as rmSync7,
|
|
14122
|
-
statSync as
|
|
14333
|
+
statSync as statSync9,
|
|
14123
14334
|
writeFileSync as writeFileSync8,
|
|
14124
14335
|
writeSync
|
|
14125
14336
|
} from "node:fs";
|
|
14126
14337
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
14127
|
-
import { join as
|
|
14338
|
+
import { join as join18 } from "node:path";
|
|
14128
14339
|
import { setTimeout as delay } from "node:timers/promises";
|
|
14129
14340
|
function helperAssembly(directory = tmpdir4()) {
|
|
14130
|
-
return
|
|
14341
|
+
return join18(directory, `gdharness-desktop-${DIGEST}.dll`);
|
|
14131
14342
|
}
|
|
14132
14343
|
async function ensureHelperCompiled(directory = tmpdir4(), budgetMs = HELPER_COMPILE_MS, wait = true) {
|
|
14133
14344
|
const assembly = helperAssembly(directory);
|
|
14134
14345
|
const lock = `${assembly}.compiling`;
|
|
14135
14346
|
const until = Date.now() + budgetMs;
|
|
14136
14347
|
while (Date.now() < until) {
|
|
14137
|
-
if (
|
|
14348
|
+
if (existsSync16(assembly)) {
|
|
14138
14349
|
return null;
|
|
14139
14350
|
}
|
|
14140
14351
|
let mine = false;
|
|
@@ -14152,8 +14363,8 @@ async function ensureHelperCompiled(directory = tmpdir4(), budgetMs = HELPER_COM
|
|
|
14152
14363
|
}
|
|
14153
14364
|
}
|
|
14154
14365
|
try {
|
|
14155
|
-
const holder = Number(
|
|
14156
|
-
const old = Date.now() -
|
|
14366
|
+
const holder = Number(readFileSync13(lock, "utf8").trim());
|
|
14367
|
+
const old = Date.now() - statSync9(lock).mtimeMs > budgetMs;
|
|
14157
14368
|
if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
|
|
14158
14369
|
rmSync7(lock, { force: true });
|
|
14159
14370
|
continue;
|
|
@@ -14167,8 +14378,8 @@ async function ensureHelperCompiled(directory = tmpdir4(), budgetMs = HELPER_COM
|
|
|
14167
14378
|
return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
|
|
14168
14379
|
}
|
|
14169
14380
|
async function compileHelper(directory, assembly, budgetMs) {
|
|
14170
|
-
const scratch = mkdtempSync2(
|
|
14171
|
-
const source =
|
|
14381
|
+
const scratch = mkdtempSync2(join18(directory, "gdharness-desktop-source-"));
|
|
14382
|
+
const source = join18(scratch, "helper.cs");
|
|
14172
14383
|
writeFileSync8(source, SOURCE, "utf8");
|
|
14173
14384
|
const literal = (path) => `'${path.replaceAll("'", "''")}'`;
|
|
14174
14385
|
const script = [
|
|
@@ -14206,7 +14417,7 @@ async function compileHelper(directory, assembly, budgetMs) {
|
|
|
14206
14417
|
});
|
|
14207
14418
|
compiler.once("close", (code) => {
|
|
14208
14419
|
clearTimeout(timer);
|
|
14209
|
-
resolve(
|
|
14420
|
+
resolve(existsSync16(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
|
|
14210
14421
|
});
|
|
14211
14422
|
});
|
|
14212
14423
|
} finally {
|
|
@@ -14431,11 +14642,11 @@ public static class GdharnessDesktop {
|
|
|
14431
14642
|
var init_desktop = __esm(() => {
|
|
14432
14643
|
init_alive();
|
|
14433
14644
|
init_spawn();
|
|
14434
|
-
DIGEST =
|
|
14645
|
+
DIGEST = createHash5("sha256").update(SOURCE).digest("hex").slice(0, 16);
|
|
14435
14646
|
});
|
|
14436
14647
|
|
|
14437
14648
|
// src/suite-judge.ts
|
|
14438
|
-
import { readFileSync as
|
|
14649
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
14439
14650
|
import { stripVTControlCharacters } from "node:util";
|
|
14440
14651
|
function secondsOf(time) {
|
|
14441
14652
|
let ms = 0;
|
|
@@ -14542,7 +14753,7 @@ function readRun(printed) {
|
|
|
14542
14753
|
}
|
|
14543
14754
|
function readExpected(file) {
|
|
14544
14755
|
try {
|
|
14545
|
-
const parsed = JSON.parse(
|
|
14756
|
+
const parsed = JSON.parse(readFileSync16(file, "utf8"));
|
|
14546
14757
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
14547
14758
|
} catch {
|
|
14548
14759
|
return {};
|
|
@@ -14776,7 +14987,7 @@ var init_whole_file = __esm(() => {
|
|
|
14776
14987
|
});
|
|
14777
14988
|
|
|
14778
14989
|
// src/suite-plan.ts
|
|
14779
|
-
import { existsSync as
|
|
14990
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync7, readFileSync as readFileSync17 } from "node:fs";
|
|
14780
14991
|
import { dirname as dirname10, posix as posix3 } from "node:path";
|
|
14781
14992
|
|
|
14782
14993
|
class ProjectScripts {
|
|
@@ -14865,7 +15076,7 @@ class ProjectScripts {
|
|
|
14865
15076
|
}
|
|
14866
15077
|
function readTimes(file) {
|
|
14867
15078
|
try {
|
|
14868
|
-
const parsed = JSON.parse(
|
|
15079
|
+
const parsed = JSON.parse(readFileSync17(file, "utf8"));
|
|
14869
15080
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
14870
15081
|
} catch {
|
|
14871
15082
|
return {};
|
|
@@ -14946,19 +15157,27 @@ function unitsOf(suites, times, splitPast = SPLIT_PAST_SECONDS) {
|
|
|
14946
15157
|
function dealt(units, declared, most) {
|
|
14947
15158
|
const windowed = units.filter((unit) => unit.windowed);
|
|
14948
15159
|
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
|
|
15160
|
+
if (units.length === 0) {
|
|
15161
|
+
return [];
|
|
15162
|
+
}
|
|
15163
|
+
const together = windowed.reduce((sum, unit) => sum + unit.cost, 0);
|
|
15164
|
+
const total = units.reduce((sum, unit) => sum + unit.cost, 0);
|
|
15165
|
+
const slowest = Math.max(together, headless[0]?.cost ?? 0);
|
|
15166
|
+
const count = Math.max(1, Math.min(most, headless.length + (windowed.length > 0 ? 1 : 0), Math.ceil(total / Math.max(slowest, ENGINE_START_SECONDS))));
|
|
15167
|
+
const bins = Array.from({ length: count }, (_, index) => index === 0 ? [...windowed] : []);
|
|
15168
|
+
const loads = Array.from({ length: count }, (_, index) => index === 0 ? together : 0);
|
|
14955
15169
|
for (const unit of headless) {
|
|
14956
15170
|
const lightest = loads.indexOf(Math.min(...loads));
|
|
14957
15171
|
bins[lightest]?.push(unit);
|
|
14958
15172
|
loads[lightest] = (loads[lightest] ?? 0) + unit.cost;
|
|
14959
15173
|
}
|
|
14960
|
-
|
|
14961
|
-
|
|
15174
|
+
return bins.map((bin, index) => planOf(bin, declared, index === 0 && windowed.length > 0));
|
|
15175
|
+
}
|
|
15176
|
+
function windowedSaid(plans) {
|
|
15177
|
+
if (!plans.some((plan) => plan.windowed)) {
|
|
15178
|
+
return "";
|
|
15179
|
+
}
|
|
15180
|
+
return plans.length === 1 ? ", with a window" : ", one of them windowed";
|
|
14962
15181
|
}
|
|
14963
15182
|
function inOneEngine(units, declared) {
|
|
14964
15183
|
return planOf(units, declared, units.some((unit) => unit.windowed));
|
|
@@ -14978,10 +15197,10 @@ function planOf(bin, declared, windowed) {
|
|
|
14978
15197
|
}
|
|
14979
15198
|
function viewportOf(projectPath) {
|
|
14980
15199
|
const file = `${projectPath}/project.godot`;
|
|
14981
|
-
if (!
|
|
15200
|
+
if (!existsSync18(file)) {
|
|
14982
15201
|
return null;
|
|
14983
15202
|
}
|
|
14984
|
-
const text =
|
|
15203
|
+
const text = readFileSync17(file, "utf8");
|
|
14985
15204
|
const width = /^window\/size\/viewport_width\s*=\s*(\d+)/m.exec(text)?.[1];
|
|
14986
15205
|
const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
|
|
14987
15206
|
return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
|
|
@@ -36597,13 +36816,13 @@ var init_stdio = __esm(() => {
|
|
|
36597
36816
|
});
|
|
36598
36817
|
|
|
36599
36818
|
// src/autoload-failure.ts
|
|
36600
|
-
import { existsSync as
|
|
36601
|
-
import { join as
|
|
36819
|
+
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "node:fs";
|
|
36820
|
+
import { join as join23 } from "node:path";
|
|
36602
36821
|
function scriptsAutoloaded(projectPath) {
|
|
36603
36822
|
const scripts = new Map;
|
|
36604
36823
|
let settings;
|
|
36605
36824
|
try {
|
|
36606
|
-
settings = parseProjectGodot(
|
|
36825
|
+
settings = parseProjectGodot(readFileSync20(join23(projectPath, "project.godot"), "utf8"));
|
|
36607
36826
|
} catch {
|
|
36608
36827
|
return scripts;
|
|
36609
36828
|
}
|
|
@@ -36612,12 +36831,12 @@ function scriptsAutoloaded(projectPath) {
|
|
|
36612
36831
|
scripts.set(`res://${path}`, name);
|
|
36613
36832
|
continue;
|
|
36614
36833
|
}
|
|
36615
|
-
const scene =
|
|
36616
|
-
if (!
|
|
36834
|
+
const scene = join23(projectPath, path);
|
|
36835
|
+
if (!existsSync20(scene)) {
|
|
36617
36836
|
continue;
|
|
36618
36837
|
}
|
|
36619
36838
|
try {
|
|
36620
|
-
for (const named of
|
|
36839
|
+
for (const named of readFileSync20(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
|
|
36621
36840
|
scripts.set(named[1] ?? "", name);
|
|
36622
36841
|
}
|
|
36623
36842
|
} catch {}
|
|
@@ -36636,10 +36855,10 @@ var init_autoload_failure = __esm(() => {
|
|
|
36636
36855
|
});
|
|
36637
36856
|
|
|
36638
36857
|
// src/runtime-client.ts
|
|
36639
|
-
import { existsSync as
|
|
36858
|
+
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync21, statSync as statSync13, unlinkSync as unlinkSync2 } from "node:fs";
|
|
36640
36859
|
import { createConnection } from "node:net";
|
|
36641
36860
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
36642
|
-
import { dirname as dirname12, join as
|
|
36861
|
+
import { dirname as dirname12, join as join24, resolve as resolve5 } from "node:path";
|
|
36643
36862
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
36644
36863
|
function runtimeDirectory(variables = process.env) {
|
|
36645
36864
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -36647,20 +36866,20 @@ function runtimeDirectory(variables = process.env) {
|
|
|
36647
36866
|
return explicit;
|
|
36648
36867
|
}
|
|
36649
36868
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
36650
|
-
return
|
|
36869
|
+
return join24(perUser ?? tmpdir5(), "gdharness");
|
|
36651
36870
|
}
|
|
36652
36871
|
function runtimeDirectories(variables = process.env) {
|
|
36653
36872
|
const candidates = [runtimeDirectory(variables)];
|
|
36654
36873
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
36655
36874
|
for (const base of fallbacks) {
|
|
36656
|
-
candidates.push(
|
|
36875
|
+
candidates.push(join24(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
36657
36876
|
}
|
|
36658
36877
|
return [...new Set(candidates.map((path) => resolve5(path)))];
|
|
36659
36878
|
}
|
|
36660
36879
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
36661
36880
|
for (const directory of directories) {
|
|
36662
|
-
const path =
|
|
36663
|
-
if (
|
|
36881
|
+
const path = join24(directory, `runtime-${pid}.log`);
|
|
36882
|
+
if (existsSync21(path)) {
|
|
36664
36883
|
return path;
|
|
36665
36884
|
}
|
|
36666
36885
|
}
|
|
@@ -36691,7 +36910,7 @@ function reportAlreadyInTranscript(transcript, report) {
|
|
|
36691
36910
|
function parseAnnouncement(file, pid) {
|
|
36692
36911
|
let fields;
|
|
36693
36912
|
try {
|
|
36694
|
-
fields = asParams(JSON.parse(
|
|
36913
|
+
fields = asParams(JSON.parse(readFileSync21(file, "utf8")));
|
|
36695
36914
|
} catch {
|
|
36696
36915
|
return { kind: "rubbish" };
|
|
36697
36916
|
}
|
|
@@ -36708,7 +36927,7 @@ function parseAnnouncement(file, pid) {
|
|
|
36708
36927
|
};
|
|
36709
36928
|
let announcedAt;
|
|
36710
36929
|
try {
|
|
36711
|
-
announcedAt =
|
|
36930
|
+
announcedAt = statSync13(file).mtimeMs;
|
|
36712
36931
|
} catch {}
|
|
36713
36932
|
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
36714
36933
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
@@ -36761,7 +36980,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
|
|
|
36761
36980
|
function wentAway(file, pid) {
|
|
36762
36981
|
let project = "";
|
|
36763
36982
|
try {
|
|
36764
|
-
const fields = asParams(JSON.parse(
|
|
36983
|
+
const fields = asParams(JSON.parse(readFileSync21(file, "utf8")));
|
|
36765
36984
|
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
36766
36985
|
} catch {
|
|
36767
36986
|
return;
|
|
@@ -36804,29 +37023,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
36804
37023
|
}).map((one) => one.file));
|
|
36805
37024
|
}
|
|
36806
37025
|
function announcedIn(directory) {
|
|
36807
|
-
if (!
|
|
37026
|
+
if (!existsSync21(directory)) {
|
|
36808
37027
|
return [];
|
|
36809
37028
|
}
|
|
36810
37029
|
const found = [];
|
|
36811
37030
|
const candidates = [];
|
|
36812
37031
|
const entries = [];
|
|
36813
|
-
for (const entry of
|
|
37032
|
+
for (const entry of readdirSync13(directory)) {
|
|
36814
37033
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
36815
37034
|
if (report) {
|
|
36816
|
-
sweepErrorReport(
|
|
37035
|
+
sweepErrorReport(join24(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
36817
37036
|
continue;
|
|
36818
37037
|
}
|
|
36819
37038
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
36820
37039
|
if (!match) {
|
|
36821
37040
|
continue;
|
|
36822
37041
|
}
|
|
36823
|
-
const file =
|
|
37042
|
+
const file = join24(directory, entry);
|
|
36824
37043
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
36825
37044
|
const running = alive(pid);
|
|
36826
37045
|
entries.push({ file, pid, running });
|
|
36827
37046
|
if (running) {
|
|
36828
37047
|
try {
|
|
36829
|
-
candidates.push({ file, pid, writtenAt:
|
|
37048
|
+
candidates.push({ file, pid, writtenAt: statSync13(file).mtimeMs });
|
|
36830
37049
|
} catch {}
|
|
36831
37050
|
}
|
|
36832
37051
|
}
|
|
@@ -36848,7 +37067,7 @@ function announcedIn(directory) {
|
|
|
36848
37067
|
found.push(announced);
|
|
36849
37068
|
}
|
|
36850
37069
|
const held = new Set(entries.map((one) => one.file));
|
|
36851
|
-
const here =
|
|
37070
|
+
const here = join24(directory, ".");
|
|
36852
37071
|
for (const file of verdicts.keys()) {
|
|
36853
37072
|
if (dirname12(file) === here && !held.has(file)) {
|
|
36854
37073
|
verdicts.delete(file);
|
|
@@ -36857,7 +37076,7 @@ function announcedIn(directory) {
|
|
|
36857
37076
|
return found;
|
|
36858
37077
|
}
|
|
36859
37078
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
36860
|
-
const files = directories.map((directory) =>
|
|
37079
|
+
const files = directories.map((directory) => join24(directory, `runtime-${pid}.json`)).filter((file) => existsSync21(file));
|
|
36861
37080
|
const first = files[0];
|
|
36862
37081
|
if (first === undefined) {
|
|
36863
37082
|
return;
|
|
@@ -36883,7 +37102,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
36883
37102
|
return;
|
|
36884
37103
|
}
|
|
36885
37104
|
try {
|
|
36886
|
-
if (now -
|
|
37105
|
+
if (now - statSync13(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
36887
37106
|
unlinkSync2(path);
|
|
36888
37107
|
}
|
|
36889
37108
|
} catch {}
|
|
@@ -37276,14 +37495,14 @@ var init_runtime_client = __esm(() => {
|
|
|
37276
37495
|
});
|
|
37277
37496
|
|
|
37278
37497
|
// src/boot-note.ts
|
|
37279
|
-
import { mkdirSync as mkdirSync9, readFileSync as
|
|
37280
|
-
import { dirname as dirname13, join as
|
|
37498
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "node:fs";
|
|
37499
|
+
import { dirname as dirname13, join as join25 } from "node:path";
|
|
37281
37500
|
function bootNotePath(projectPath) {
|
|
37282
|
-
return
|
|
37501
|
+
return join25(projectPath, ".godot", "gdharness-boot.json");
|
|
37283
37502
|
}
|
|
37284
37503
|
function readScenes(projectPath) {
|
|
37285
37504
|
try {
|
|
37286
|
-
return asParams(asParams(JSON.parse(
|
|
37505
|
+
return asParams(asParams(JSON.parse(readFileSync22(bootNotePath(projectPath), "utf8")))["scenes"]);
|
|
37287
37506
|
} catch {
|
|
37288
37507
|
return {};
|
|
37289
37508
|
}
|
|
@@ -37325,10 +37544,10 @@ var init_boot_note = __esm(() => {
|
|
|
37325
37544
|
});
|
|
37326
37545
|
|
|
37327
37546
|
// src/breakpoint-note.ts
|
|
37328
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
37329
|
-
import { dirname as dirname14, isAbsolute as isAbsolute5, join as
|
|
37547
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync23, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
37548
|
+
import { dirname as dirname14, isAbsolute as isAbsolute5, join as join26, relative as relative4 } from "node:path";
|
|
37330
37549
|
function breakpointNotePath(projectPath) {
|
|
37331
|
-
return
|
|
37550
|
+
return join26(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
37332
37551
|
}
|
|
37333
37552
|
function writeBreakpointNote(projectPath, held) {
|
|
37334
37553
|
const path = breakpointNotePath(projectPath);
|
|
@@ -37354,7 +37573,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
37354
37573
|
}
|
|
37355
37574
|
function readBreakpointNote(projectPath) {
|
|
37356
37575
|
try {
|
|
37357
|
-
const parsed = JSON.parse(
|
|
37576
|
+
const parsed = JSON.parse(readFileSync23(breakpointNotePath(projectPath), "utf8"));
|
|
37358
37577
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
|
|
37359
37578
|
return [];
|
|
37360
37579
|
}
|
|
@@ -37369,7 +37588,7 @@ function readBreakpointNote(projectPath) {
|
|
|
37369
37588
|
}
|
|
37370
37589
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
37371
37590
|
if (lines.length > 0) {
|
|
37372
|
-
held.push({ scriptPath: realAsFarAsItExists(
|
|
37591
|
+
held.push({ scriptPath: realAsFarAsItExists(join26(projectPath, script)), lines });
|
|
37373
37592
|
}
|
|
37374
37593
|
}
|
|
37375
37594
|
return held;
|
|
@@ -37387,14 +37606,14 @@ var init_breakpoint_note = __esm(() => {
|
|
|
37387
37606
|
});
|
|
37388
37607
|
|
|
37389
37608
|
// src/class-note.ts
|
|
37390
|
-
import { mkdirSync as mkdirSync11, readFileSync as
|
|
37391
|
-
import { dirname as dirname15, join as
|
|
37609
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync24 } from "node:fs";
|
|
37610
|
+
import { dirname as dirname15, join as join27 } from "node:path";
|
|
37392
37611
|
function classNotePath(projectPath) {
|
|
37393
|
-
return
|
|
37612
|
+
return join27(projectPath, ".godot", "gdharness-classes.json");
|
|
37394
37613
|
}
|
|
37395
37614
|
function readClassNote(projectPath) {
|
|
37396
37615
|
try {
|
|
37397
|
-
const fields = asParams(JSON.parse(
|
|
37616
|
+
const fields = asParams(JSON.parse(readFileSync24(classNotePath(projectPath), "utf8")));
|
|
37398
37617
|
const rebuiltWith = readArray(fields, "rebuiltWith");
|
|
37399
37618
|
if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
|
|
37400
37619
|
return null;
|
|
@@ -37436,16 +37655,16 @@ var init_class_note = __esm(() => {
|
|
|
37436
37655
|
});
|
|
37437
37656
|
|
|
37438
37657
|
// src/config-pin.ts
|
|
37439
|
-
import { existsSync as
|
|
37440
|
-
import { join as
|
|
37658
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25 } from "node:fs";
|
|
37659
|
+
import { join as join28 } from "node:path";
|
|
37441
37660
|
function versionsNamedInConfig(projectPath) {
|
|
37442
|
-
const config =
|
|
37443
|
-
if (!
|
|
37661
|
+
const config = join28(projectPath, ".mcp.json");
|
|
37662
|
+
if (!existsSync22(config)) {
|
|
37444
37663
|
return [];
|
|
37445
37664
|
}
|
|
37446
37665
|
let text;
|
|
37447
37666
|
try {
|
|
37448
|
-
text =
|
|
37667
|
+
text = readFileSync25(config, "utf8");
|
|
37449
37668
|
JSON.parse(text);
|
|
37450
37669
|
} catch {
|
|
37451
37670
|
return [];
|
|
@@ -37466,10 +37685,10 @@ var init_config_pin = __esm(() => {
|
|
|
37466
37685
|
});
|
|
37467
37686
|
|
|
37468
37687
|
// src/cut-test-run.ts
|
|
37469
|
-
import { existsSync as
|
|
37470
|
-
import { dirname as dirname16, join as
|
|
37688
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync12, writeFileSync as writeFileSync13 } from "node:fs";
|
|
37689
|
+
import { dirname as dirname16, join as join29 } from "node:path";
|
|
37471
37690
|
function keptAt(projectPath) {
|
|
37472
|
-
return
|
|
37691
|
+
return join29(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
37473
37692
|
}
|
|
37474
37693
|
function keepCutTestRun(projectPath, run) {
|
|
37475
37694
|
const path = keptAt(projectPath);
|
|
@@ -37481,11 +37700,11 @@ function keepCutTestRun(projectPath, run) {
|
|
|
37481
37700
|
}
|
|
37482
37701
|
function takeCutTestRun(projectPath) {
|
|
37483
37702
|
const path = keptAt(projectPath);
|
|
37484
|
-
if (!
|
|
37703
|
+
if (!existsSync23(path)) {
|
|
37485
37704
|
return null;
|
|
37486
37705
|
}
|
|
37487
37706
|
try {
|
|
37488
|
-
const run = JSON.parse(
|
|
37707
|
+
const run = JSON.parse(readFileSync26(path, "utf8"));
|
|
37489
37708
|
rmSync12(path, { force: true });
|
|
37490
37709
|
return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
|
|
37491
37710
|
} catch {
|
|
@@ -38298,25 +38517,25 @@ var init_dap_client = __esm(() => {
|
|
|
38298
38517
|
// src/editor-log.ts
|
|
38299
38518
|
import {
|
|
38300
38519
|
closeSync as closeSync4,
|
|
38301
|
-
existsSync as
|
|
38520
|
+
existsSync as existsSync24,
|
|
38302
38521
|
mkdirSync as mkdirSync13,
|
|
38303
38522
|
openSync as openSync4,
|
|
38304
|
-
readFileSync as
|
|
38523
|
+
readFileSync as readFileSync27,
|
|
38305
38524
|
readSync as readSync2,
|
|
38306
38525
|
rmSync as rmSync13,
|
|
38307
|
-
statSync as
|
|
38526
|
+
statSync as statSync14,
|
|
38308
38527
|
writeFileSync as writeFileSync14
|
|
38309
38528
|
} from "node:fs";
|
|
38310
|
-
import { dirname as dirname17, join as
|
|
38529
|
+
import { dirname as dirname17, join as join30 } from "node:path";
|
|
38311
38530
|
function editorLogPath(projectPath) {
|
|
38312
|
-
return
|
|
38531
|
+
return join30(projectPath, ".godot", "gdharness-editor.log");
|
|
38313
38532
|
}
|
|
38314
38533
|
function editorLogNotePath(projectPath) {
|
|
38315
|
-
return
|
|
38534
|
+
return join30(projectPath, ".godot", "gdharness-editor.json");
|
|
38316
38535
|
}
|
|
38317
38536
|
function readEditorLogNote(projectPath) {
|
|
38318
38537
|
try {
|
|
38319
|
-
const fields = asParams(JSON.parse(
|
|
38538
|
+
const fields = asParams(JSON.parse(readFileSync27(editorLogNotePath(projectPath), "utf8")));
|
|
38320
38539
|
const pid = readNumber(fields, "pid");
|
|
38321
38540
|
const startedAt = readString(fields, "startedAt");
|
|
38322
38541
|
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
@@ -38357,7 +38576,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
38357
38576
|
return { kind: "another editor", notedPid: noted?.pid ?? null };
|
|
38358
38577
|
}
|
|
38359
38578
|
const path = editorLogPath(projectPath);
|
|
38360
|
-
if (!
|
|
38579
|
+
if (!existsSync24(path)) {
|
|
38361
38580
|
return { kind: "not written" };
|
|
38362
38581
|
}
|
|
38363
38582
|
const log = new GameLog;
|
|
@@ -38413,9 +38632,9 @@ function foldProgress(text) {
|
|
|
38413
38632
|
`);
|
|
38414
38633
|
}
|
|
38415
38634
|
function theFirstOf(path) {
|
|
38416
|
-
const whole =
|
|
38635
|
+
const whole = statSync14(path).size;
|
|
38417
38636
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
38418
|
-
return { text:
|
|
38637
|
+
return { text: readFileSync27(path, "utf8") };
|
|
38419
38638
|
}
|
|
38420
38639
|
const handle = openSync4(path, "r");
|
|
38421
38640
|
try {
|
|
@@ -38576,7 +38795,7 @@ var init_editor_log = __esm(() => {
|
|
|
38576
38795
|
});
|
|
38577
38796
|
|
|
38578
38797
|
// src/rename.ts
|
|
38579
|
-
import { existsSync as
|
|
38798
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync28, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "node:fs";
|
|
38580
38799
|
import { dirname as dirname18, posix as posix4 } from "node:path";
|
|
38581
38800
|
|
|
38582
38801
|
class Script {
|
|
@@ -39281,11 +39500,11 @@ function rewritesOf(plan, texts, move) {
|
|
|
39281
39500
|
}
|
|
39282
39501
|
function applyRewrites(rewrites) {
|
|
39283
39502
|
for (const rewrite of rewrites) {
|
|
39284
|
-
const now =
|
|
39503
|
+
const now = readFileSync28(rewrite.absolute, "utf8");
|
|
39285
39504
|
if (now !== rewrite.before) {
|
|
39286
39505
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
39287
39506
|
}
|
|
39288
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
39507
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync25(rewrite.writeTo)) {
|
|
39289
39508
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
39290
39509
|
}
|
|
39291
39510
|
}
|
|
@@ -39299,7 +39518,7 @@ function applyRewrites(rewrites) {
|
|
|
39299
39518
|
let movedUid = false;
|
|
39300
39519
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
39301
39520
|
unlinkSync3(rewrite.absolute);
|
|
39302
|
-
if (
|
|
39521
|
+
if (existsSync25(`${rewrite.absolute}.uid`)) {
|
|
39303
39522
|
renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
39304
39523
|
movedUid = true;
|
|
39305
39524
|
}
|
|
@@ -39345,10 +39564,10 @@ var init_rename = __esm(() => {
|
|
|
39345
39564
|
});
|
|
39346
39565
|
|
|
39347
39566
|
// src/lsp_client.ts
|
|
39348
|
-
import { existsSync as
|
|
39567
|
+
import { existsSync as existsSync26, realpathSync as realpathSync3 } from "node:fs";
|
|
39349
39568
|
import { readFile, realpath } from "node:fs/promises";
|
|
39350
39569
|
import { createConnection as createConnection3 } from "node:net";
|
|
39351
|
-
import { dirname as dirname19, join as
|
|
39570
|
+
import { dirname as dirname19, join as join31, resolve as resolve6 } from "node:path";
|
|
39352
39571
|
import { setTimeout as delay6 } from "node:timers/promises";
|
|
39353
39572
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
39354
39573
|
function initializeTimeoutMs() {
|
|
@@ -40026,7 +40245,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
40026
40245
|
} catch {
|
|
40027
40246
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
40028
40247
|
}
|
|
40029
|
-
if (!
|
|
40248
|
+
if (!existsSync26(join31(projectPath, "project.godot"))) {
|
|
40030
40249
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
40031
40250
|
}
|
|
40032
40251
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -40186,8 +40405,8 @@ var init_lsp_client = __esm(() => {
|
|
|
40186
40405
|
});
|
|
40187
40406
|
|
|
40188
40407
|
// src/rename-symbol.ts
|
|
40189
|
-
import { existsSync as
|
|
40190
|
-
import { basename as basename8, dirname as dirname20, join as
|
|
40408
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync15, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync14, writeFileSync as writeFileSync16 } from "node:fs";
|
|
40409
|
+
import { basename as basename8, dirname as dirname20, join as join32, relative as relative5, resolve as resolve7 } from "node:path";
|
|
40191
40410
|
function resourcePathOf(projectPath, absolute) {
|
|
40192
40411
|
const inside = relative5(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
40193
40412
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
@@ -40200,7 +40419,7 @@ function onDiskSpelling(path) {
|
|
|
40200
40419
|
let at = resolve7(path);
|
|
40201
40420
|
for (;; ) {
|
|
40202
40421
|
try {
|
|
40203
|
-
return
|
|
40422
|
+
return join32(realpathSync4.native(at), ...missing.reverse());
|
|
40204
40423
|
} catch {
|
|
40205
40424
|
const parent = dirname20(at);
|
|
40206
40425
|
if (parent === at) {
|
|
@@ -40254,7 +40473,7 @@ async function renameSymbol(request, services) {
|
|
|
40254
40473
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
40255
40474
|
};
|
|
40256
40475
|
}
|
|
40257
|
-
if (
|
|
40476
|
+
if (existsSync27(request.newScriptPath)) {
|
|
40258
40477
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
40259
40478
|
}
|
|
40260
40479
|
}
|
|
@@ -40430,7 +40649,7 @@ async function renameSymbol(request, services) {
|
|
|
40430
40649
|
scripts,
|
|
40431
40650
|
moves: new Map([[declaringPath, moveTo]]),
|
|
40432
40651
|
named: [declaringPath],
|
|
40433
|
-
exists: (path) =>
|
|
40652
|
+
exists: (path) => existsSync27(`${projectPath}/${path.slice("res://".length)}`)
|
|
40434
40653
|
}, plan);
|
|
40435
40654
|
}
|
|
40436
40655
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
|
|
@@ -40629,15 +40848,15 @@ ${mention.kind}`;
|
|
|
40629
40848
|
};
|
|
40630
40849
|
}
|
|
40631
40850
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
40632
|
-
const directory =
|
|
40851
|
+
const directory = join32(projectPath, ".godot", "gdharness-renames");
|
|
40633
40852
|
mkdirSync15(directory, { recursive: true });
|
|
40634
40853
|
detailWrites += 1;
|
|
40635
|
-
const path =
|
|
40854
|
+
const path = join32(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
40636
40855
|
writeFileSync16(path, `${JSON.stringify(detail, null, 2)}
|
|
40637
40856
|
`, "utf8");
|
|
40638
|
-
const kept =
|
|
40857
|
+
const kept = readdirSync14(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
40639
40858
|
for (const old of kept.slice(10)) {
|
|
40640
|
-
rmSync14(
|
|
40859
|
+
rmSync14(join32(directory, old), { force: true });
|
|
40641
40860
|
}
|
|
40642
40861
|
return path;
|
|
40643
40862
|
}
|
|
@@ -40678,25 +40897,25 @@ var init_rename_symbol = __esm(() => {
|
|
|
40678
40897
|
|
|
40679
40898
|
// src/file-move.ts
|
|
40680
40899
|
import {
|
|
40681
|
-
existsSync as
|
|
40900
|
+
existsSync as existsSync28,
|
|
40682
40901
|
mkdirSync as mkdirSync16,
|
|
40683
|
-
readdirSync as
|
|
40684
|
-
readFileSync as
|
|
40902
|
+
readdirSync as readdirSync15,
|
|
40903
|
+
readFileSync as readFileSync29,
|
|
40685
40904
|
renameSync as renameSync4,
|
|
40686
|
-
statSync as
|
|
40905
|
+
statSync as statSync15,
|
|
40687
40906
|
writeFileSync as writeFileSync17
|
|
40688
40907
|
} from "node:fs";
|
|
40689
|
-
import { basename as basename9, dirname as dirname21, extname as extname3, join as
|
|
40908
|
+
import { basename as basename9, dirname as dirname21, extname as extname3, join as join33, relative as relative6 } from "node:path";
|
|
40690
40909
|
function filesUnder(directory) {
|
|
40691
|
-
return
|
|
40692
|
-
const path =
|
|
40910
|
+
return readdirSync15(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
40911
|
+
const path = join33(directory, entry.name);
|
|
40693
40912
|
return entry.isDirectory() ? filesUnder(path) : [path];
|
|
40694
40913
|
});
|
|
40695
40914
|
}
|
|
40696
40915
|
function sameFile(one, other) {
|
|
40697
40916
|
try {
|
|
40698
|
-
const a =
|
|
40699
|
-
const b =
|
|
40917
|
+
const a = statSync15(one);
|
|
40918
|
+
const b = statSync15(other);
|
|
40700
40919
|
return a.ino === b.ino && a.dev === b.dev;
|
|
40701
40920
|
} catch {
|
|
40702
40921
|
return false;
|
|
@@ -40710,8 +40929,8 @@ function unseenByTheEngine(projectPath, path) {
|
|
|
40710
40929
|
return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
|
|
40711
40930
|
}
|
|
40712
40931
|
if (at < steps.length - 1) {
|
|
40713
|
-
directory =
|
|
40714
|
-
if (
|
|
40932
|
+
directory = join33(directory, step);
|
|
40933
|
+
if (existsSync28(join33(directory, ".gdignore"))) {
|
|
40715
40934
|
return `${path} is under a directory holding a .gdignore, which the engine does not load`;
|
|
40716
40935
|
}
|
|
40717
40936
|
}
|
|
@@ -40723,21 +40942,21 @@ function installedAddonOf(projectPath, path) {
|
|
|
40723
40942
|
if (steps[0] !== "addons" || steps[1] === undefined) {
|
|
40724
40943
|
return null;
|
|
40725
40944
|
}
|
|
40726
|
-
return
|
|
40945
|
+
return existsSync28(join33(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
|
|
40727
40946
|
}
|
|
40728
40947
|
function binaryResources(projectPath) {
|
|
40729
40948
|
const found = [];
|
|
40730
40949
|
const visit = (directory, prefix) => {
|
|
40731
|
-
if (
|
|
40950
|
+
if (existsSync28(join33(directory, ".gdignore"))) {
|
|
40732
40951
|
return;
|
|
40733
40952
|
}
|
|
40734
|
-
for (const entry of
|
|
40953
|
+
for (const entry of readdirSync15(directory, { withFileTypes: true })) {
|
|
40735
40954
|
if (entry.name.startsWith(".")) {
|
|
40736
40955
|
continue;
|
|
40737
40956
|
}
|
|
40738
40957
|
const path = `${prefix}${entry.name}`;
|
|
40739
40958
|
if (entry.isDirectory()) {
|
|
40740
|
-
visit(
|
|
40959
|
+
visit(join33(directory, entry.name), `${path}/`);
|
|
40741
40960
|
} else if (/\.(scn|res)$/i.test(entry.name)) {
|
|
40742
40961
|
found.push(path);
|
|
40743
40962
|
}
|
|
@@ -40747,7 +40966,7 @@ function binaryResources(projectPath) {
|
|
|
40747
40966
|
return found;
|
|
40748
40967
|
}
|
|
40749
40968
|
function targetPathOf(projectPath, absolute) {
|
|
40750
|
-
const inside = relative6(onDiskSpelling(projectPath),
|
|
40969
|
+
const inside = relative6(onDiskSpelling(projectPath), join33(onDiskSpelling(dirname21(absolute)), basename9(absolute)));
|
|
40751
40970
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
40752
40971
|
return null;
|
|
40753
40972
|
}
|
|
@@ -40765,13 +40984,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40765
40984
|
if (to === null) {
|
|
40766
40985
|
return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
|
|
40767
40986
|
}
|
|
40768
|
-
if (!
|
|
40987
|
+
if (!existsSync28(absoluteFrom)) {
|
|
40769
40988
|
return { ok: false, reason: `There is nothing at ${from} to move.` };
|
|
40770
40989
|
}
|
|
40771
40990
|
if (from === to) {
|
|
40772
40991
|
return { ok: false, reason: `${from} is already there; nothing to move.` };
|
|
40773
40992
|
}
|
|
40774
|
-
const isDirectory =
|
|
40993
|
+
const isDirectory = statSync15(absoluteFrom).isDirectory();
|
|
40775
40994
|
if (from === "res://project.godot") {
|
|
40776
40995
|
return {
|
|
40777
40996
|
ok: false,
|
|
@@ -40794,7 +41013,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40794
41013
|
if (isDirectory && to.startsWith(`${from}/`)) {
|
|
40795
41014
|
return { ok: false, reason: `${from} cannot be moved into itself.` };
|
|
40796
41015
|
}
|
|
40797
|
-
if (
|
|
41016
|
+
if (existsSync28(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
|
|
40798
41017
|
return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
|
|
40799
41018
|
}
|
|
40800
41019
|
const unseen = unseenByTheEngine(projectPath, to);
|
|
@@ -40802,7 +41021,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40802
41021
|
return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
|
|
40803
41022
|
}
|
|
40804
41023
|
if (!isDirectory) {
|
|
40805
|
-
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null &&
|
|
41024
|
+
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
41025
|
if (owner !== undefined && owner !== null) {
|
|
40807
41026
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
40808
41027
|
}
|
|
@@ -40824,7 +41043,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40824
41043
|
} else {
|
|
40825
41044
|
moves.set(from, to);
|
|
40826
41045
|
for (const sidecar of SIDECARS) {
|
|
40827
|
-
if (
|
|
41046
|
+
if (existsSync28(`${absoluteFrom}${sidecar}`)) {
|
|
40828
41047
|
moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
|
|
40829
41048
|
}
|
|
40830
41049
|
}
|
|
@@ -40836,12 +41055,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40836
41055
|
scripts,
|
|
40837
41056
|
moves,
|
|
40838
41057
|
named: [from],
|
|
40839
|
-
exists: (path) =>
|
|
41058
|
+
exists: (path) => existsSync28(join33(projectPath, path.slice("res://".length)))
|
|
40840
41059
|
}, { edits: [], mentions: [] });
|
|
40841
41060
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
|
|
40842
41061
|
const binaries = [];
|
|
40843
41062
|
for (const path of binaryResources(projectPath)) {
|
|
40844
|
-
const bytes =
|
|
41063
|
+
const bytes = readFileSync29(join33(projectPath, path.slice("res://".length)));
|
|
40845
41064
|
const names = [...moves.keys()].find((moving) => bytes.includes(moving));
|
|
40846
41065
|
if (names !== undefined) {
|
|
40847
41066
|
binaries.push({
|
|
@@ -40860,7 +41079,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40860
41079
|
}));
|
|
40861
41080
|
const left = leftAloneReport(mentions);
|
|
40862
41081
|
const rewrites = rewritesOf(plan, texts, null);
|
|
40863
|
-
const absoluteOf = (path) =>
|
|
41082
|
+
const absoluteOf = (path) => join33(projectPath, path.slice("res://".length));
|
|
40864
41083
|
const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
|
|
40865
41084
|
const moved = [...moves].map(([source, target]) => {
|
|
40866
41085
|
const rewrite = rewritten.get(source);
|
|
@@ -40902,18 +41121,18 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
40902
41121
|
}
|
|
40903
41122
|
function applyFileMove(outcome) {
|
|
40904
41123
|
for (const rewrite of outcome.inPlace) {
|
|
40905
|
-
if (
|
|
41124
|
+
if (readFileSync29(rewrite.absolute, "utf8") !== rewrite.before) {
|
|
40906
41125
|
throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
|
|
40907
41126
|
}
|
|
40908
41127
|
}
|
|
40909
41128
|
for (const file of outcome.moved) {
|
|
40910
|
-
if (!
|
|
41129
|
+
if (!existsSync28(file.absoluteFrom)) {
|
|
40911
41130
|
throw new Error(`${file.from} is gone from disk; nothing was written`);
|
|
40912
41131
|
}
|
|
40913
|
-
if (file.text !== null &&
|
|
41132
|
+
if (file.text !== null && readFileSync29(file.absoluteFrom, "utf8") !== file.text.before) {
|
|
40914
41133
|
throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
|
|
40915
41134
|
}
|
|
40916
|
-
if (
|
|
41135
|
+
if (existsSync28(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
|
|
40917
41136
|
throw new Error(`${file.to} already exists; nothing was written`);
|
|
40918
41137
|
}
|
|
40919
41138
|
}
|
|
@@ -44580,168 +44799,6 @@ var init_godot_bridge = __esm(() => {
|
|
|
44580
44799
|
};
|
|
44581
44800
|
});
|
|
44582
44801
|
|
|
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
44802
|
// src/keeper-channel.ts
|
|
44746
44803
|
import { connect as connect2, createServer as createServer2 } from "node:net";
|
|
44747
44804
|
async function askTheKeeperToStop(address, withinMs) {
|
|
@@ -49580,6 +49637,9 @@ ${error.text}`;
|
|
|
49580
49637
|
const uncaughtFolded = foldedRepeats(uncaught.map(aboveTheRunner));
|
|
49581
49638
|
const listedAsUncaught = new Set(uncaughtFolded.map((entry) => entry.index));
|
|
49582
49639
|
const notApplied = ignoredYetRan(report, ignored);
|
|
49640
|
+
const scripts = ignored.length === 0 ? null : new ProjectScripts(project.value.path);
|
|
49641
|
+
const ignoreRead = ignoredLeftOut(report, ignored, asked.flatMap((path) => scripts === null ? [] : path.endsWith(".gd") ? scripts.suite(path) ?? [] : scripts.suitesUnder([path])));
|
|
49642
|
+
const unmatched = ignoreRead.unmatched.filter((entry) => !notApplied.some((one) => one.entry === entry));
|
|
49583
49643
|
const testsNotRun = [
|
|
49584
49644
|
...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
49645
|
];
|
|
@@ -49613,6 +49673,8 @@ ${error.text}`;
|
|
|
49613
49673
|
orphans: orphans.total > 0 ? orphans.total : undefined,
|
|
49614
49674
|
notRun: notRun > 0 ? notRun : undefined,
|
|
49615
49675
|
ignoreNotApplied: notApplied.length > 0 ? notApplied : undefined,
|
|
49676
|
+
ignored: ignoreRead.leftOut.length > 0 ? ignoreRead.leftOut : undefined,
|
|
49677
|
+
ignoreUnmatched: unmatched.length > 0 ? unmatched : undefined,
|
|
49616
49678
|
...printedLines,
|
|
49617
49679
|
savesNote,
|
|
49618
49680
|
...scanned,
|
|
@@ -49621,6 +49683,7 @@ ${error.text}`;
|
|
|
49621
49683
|
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
49684
|
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
49685
|
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,
|
|
49686
|
+
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
49687
|
uncaughtNote(uncaught),
|
|
49625
49688
|
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
49689
|
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,
|
|
@@ -53807,6 +53870,7 @@ var init_server = __esm(() => {
|
|
|
53807
53870
|
});
|
|
53808
53871
|
PROJECT_INFO_SECTIONS2 = dictionary({
|
|
53809
53872
|
autoloads: { operation: "list_autoloads", params: () => ({}) },
|
|
53873
|
+
classes: { operation: "list_script_classes", params: () => ({}) },
|
|
53810
53874
|
input_actions: { operation: "list_input_actions", params: () => ({}) },
|
|
53811
53875
|
plugins: { operation: "list_plugins", params: () => ({}) },
|
|
53812
53876
|
export_presets: { operation: "list_export_presets", params: () => ({}) },
|
|
@@ -54819,10 +54883,10 @@ init_setup();
|
|
|
54819
54883
|
init_setup();
|
|
54820
54884
|
init_tool_definitions();
|
|
54821
54885
|
init_tool_reference();
|
|
54822
|
-
import { existsSync as
|
|
54823
|
-
import { basename as basename7, dirname as dirname9, join as
|
|
54886
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
54887
|
+
import { basename as basename7, dirname as dirname9, join as join17 } from "node:path";
|
|
54824
54888
|
var SKILL_NAME = "gdharness";
|
|
54825
|
-
var SHARED_SKILLS =
|
|
54889
|
+
var SHARED_SKILLS = join17(".agents", "skills");
|
|
54826
54890
|
function skillMarkdown(version) {
|
|
54827
54891
|
return `---
|
|
54828
54892
|
name: ${SKILL_NAME}
|
|
@@ -55059,27 +55123,27 @@ what they report while starting comes back under \`engine_messages_before_operat
|
|
|
55059
55123
|
function skillFiles(version) {
|
|
55060
55124
|
return new Map([
|
|
55061
55125
|
["SKILL.md", skillMarkdown(version)],
|
|
55062
|
-
[
|
|
55126
|
+
[join17("references", "tools.md"), renderToolsMarkdown()]
|
|
55063
55127
|
]);
|
|
55064
55128
|
}
|
|
55065
55129
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
55066
|
-
const directories = [
|
|
55130
|
+
const directories = [join17(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
55067
55131
|
for (const harness of harnesses) {
|
|
55068
55132
|
if (harness.skills === undefined) {
|
|
55069
55133
|
continue;
|
|
55070
55134
|
}
|
|
55071
|
-
const own =
|
|
55072
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
55073
|
-
directories.push(
|
|
55135
|
+
const own = join17(projectPath, harness.skills.dir);
|
|
55136
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join17(own, SKILL_NAME))) {
|
|
55137
|
+
directories.push(join17(own, SKILL_NAME));
|
|
55074
55138
|
}
|
|
55075
55139
|
}
|
|
55076
55140
|
return directories;
|
|
55077
55141
|
}
|
|
55078
55142
|
function everySkillDirectory(projectPath, harnesses) {
|
|
55079
|
-
const directories = new Set([
|
|
55143
|
+
const directories = new Set([join17(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
55080
55144
|
for (const harness of harnesses) {
|
|
55081
55145
|
if (harness.skills !== undefined) {
|
|
55082
|
-
directories.add(
|
|
55146
|
+
directories.add(join17(projectPath, harness.skills.dir, SKILL_NAME));
|
|
55083
55147
|
}
|
|
55084
55148
|
}
|
|
55085
55149
|
return [...directories];
|
|
@@ -55087,7 +55151,7 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
55087
55151
|
function removeSkill(directories, projectPath) {
|
|
55088
55152
|
const removed = [];
|
|
55089
55153
|
for (const directory of directories) {
|
|
55090
|
-
if (
|
|
55154
|
+
if (existsSync15(directory)) {
|
|
55091
55155
|
rmSync6(directory, { recursive: true, force: true });
|
|
55092
55156
|
pruneEmpty(dirname9(directory), projectPath);
|
|
55093
55157
|
removed.push(directory);
|
|
@@ -55096,10 +55160,10 @@ function removeSkill(directories, projectPath) {
|
|
|
55096
55160
|
return removed;
|
|
55097
55161
|
}
|
|
55098
55162
|
function pruneEmpty(directory, projectPath) {
|
|
55099
|
-
const shared =
|
|
55163
|
+
const shared = join17(projectPath, ".agents");
|
|
55100
55164
|
let path = directory;
|
|
55101
55165
|
while (path !== projectPath && (basename7(path) === "skills" || path === shared)) {
|
|
55102
|
-
if (
|
|
55166
|
+
if (readdirSync10(path).length > 0) {
|
|
55103
55167
|
return;
|
|
55104
55168
|
}
|
|
55105
55169
|
rmSync6(path, { recursive: true, force: true });
|
|
@@ -55109,10 +55173,10 @@ function pruneEmpty(directory, projectPath) {
|
|
|
55109
55173
|
function writeSkill(directories, version) {
|
|
55110
55174
|
const files = skillFiles(version);
|
|
55111
55175
|
return directories.map((directory) => {
|
|
55112
|
-
const replaced =
|
|
55176
|
+
const replaced = existsSync15(directory);
|
|
55113
55177
|
rmSync6(directory, { recursive: true, force: true });
|
|
55114
55178
|
for (const [name, contents] of files) {
|
|
55115
|
-
const path =
|
|
55179
|
+
const path = join17(directory, name);
|
|
55116
55180
|
mkdirSync5(dirname9(path), { recursive: true });
|
|
55117
55181
|
writeFileSync7(path, contents, "utf8");
|
|
55118
55182
|
}
|
|
@@ -55125,27 +55189,27 @@ init_alive();
|
|
|
55125
55189
|
init_desktop();
|
|
55126
55190
|
import {
|
|
55127
55191
|
closeSync as closeSync3,
|
|
55128
|
-
existsSync as
|
|
55192
|
+
existsSync as existsSync19,
|
|
55129
55193
|
fstatSync,
|
|
55130
55194
|
mkdirSync as mkdirSync8,
|
|
55131
55195
|
openSync as openSync3,
|
|
55132
|
-
readdirSync as
|
|
55133
|
-
readFileSync as
|
|
55196
|
+
readdirSync as readdirSync12,
|
|
55197
|
+
readFileSync as readFileSync19,
|
|
55134
55198
|
readSync,
|
|
55135
55199
|
rmSync as rmSync10,
|
|
55136
|
-
statSync as
|
|
55200
|
+
statSync as statSync12,
|
|
55137
55201
|
writeFileSync as writeFileSync10
|
|
55138
55202
|
} from "node:fs";
|
|
55139
55203
|
import { availableParallelism, freemem, totalmem } from "node:os";
|
|
55140
|
-
import { isAbsolute as isAbsolute4, join as
|
|
55204
|
+
import { isAbsolute as isAbsolute4, join as join22, relative as relative3, resolve as resolve4, sep as sep2 } from "node:path";
|
|
55141
55205
|
import process12 from "node:process";
|
|
55142
55206
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
55143
55207
|
|
|
55144
55208
|
// src/engine-slots.ts
|
|
55145
55209
|
init_alive();
|
|
55146
55210
|
init_process_children();
|
|
55147
|
-
import { closeSync as closeSync2, mkdirSync as mkdirSync6, openSync as openSync2, readFileSync as
|
|
55148
|
-
import { join as
|
|
55211
|
+
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";
|
|
55212
|
+
import { join as join19 } from "node:path";
|
|
55149
55213
|
import process11 from "node:process";
|
|
55150
55214
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
55151
55215
|
var START_TOLERANCE_MS = 2000;
|
|
@@ -55196,7 +55260,7 @@ class EngineSlots {
|
|
|
55196
55260
|
for (;; ) {
|
|
55197
55261
|
signal?.throwIfAborted();
|
|
55198
55262
|
for (let slot = 0;slot < this.count; slot += 1) {
|
|
55199
|
-
const lock =
|
|
55263
|
+
const lock = join19(this.directory, `${String(slot)}.lock`);
|
|
55200
55264
|
if (this.made(lock) || await this.takenOver(lock)) {
|
|
55201
55265
|
this.held.add(lock);
|
|
55202
55266
|
return lock;
|
|
@@ -55213,7 +55277,7 @@ class EngineSlots {
|
|
|
55213
55277
|
release(lock) {
|
|
55214
55278
|
this.held.delete(lock);
|
|
55215
55279
|
try {
|
|
55216
|
-
if (holderOf(
|
|
55280
|
+
if (holderOf(readFileSync14(lock, "utf8"))?.pid === this.holder.pid) {
|
|
55217
55281
|
rmSync8(lock, { force: true });
|
|
55218
55282
|
}
|
|
55219
55283
|
} catch {}
|
|
@@ -55239,7 +55303,7 @@ class EngineSlots {
|
|
|
55239
55303
|
const steal = `${lock}.steal`;
|
|
55240
55304
|
if (!this.made(steal)) {
|
|
55241
55305
|
try {
|
|
55242
|
-
if (Date.now() -
|
|
55306
|
+
if (Date.now() - statSync10(steal).mtimeMs > UNWRITTEN_MS) {
|
|
55243
55307
|
rmSync8(steal, { force: true });
|
|
55244
55308
|
}
|
|
55245
55309
|
} catch {}
|
|
@@ -55263,8 +55327,8 @@ class EngineSlots {
|
|
|
55263
55327
|
let content;
|
|
55264
55328
|
let writtenAt;
|
|
55265
55329
|
try {
|
|
55266
|
-
content =
|
|
55267
|
-
writtenAt =
|
|
55330
|
+
content = readFileSync14(lock, "utf8");
|
|
55331
|
+
writtenAt = statSync10(lock).mtimeMs;
|
|
55268
55332
|
} catch {
|
|
55269
55333
|
return false;
|
|
55270
55334
|
}
|
|
@@ -55291,9 +55355,9 @@ init_spawn();
|
|
|
55291
55355
|
|
|
55292
55356
|
// src/suite-fingerprint.ts
|
|
55293
55357
|
init_class_cache();
|
|
55294
|
-
import { createHash as
|
|
55295
|
-
import { existsSync as
|
|
55296
|
-
import { join as
|
|
55358
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
55359
|
+
import { existsSync as existsSync17, readdirSync as readdirSync11, readFileSync as readFileSync15, statSync as statSync11 } from "node:fs";
|
|
55360
|
+
import { join as join20, posix as posix2 } from "node:path";
|
|
55297
55361
|
var RES_LITERAL = /res:\/\/[^"'\s)\]<>]*/g;
|
|
55298
55362
|
var RELATIVE_LOAD = /\b(?:pre)?load\(\s*["']((?!res:\/\/|uid:\/\/)[^"']+)["']/g;
|
|
55299
55363
|
var REACHES_AT_RUN_TIME = [
|
|
@@ -55313,7 +55377,7 @@ class SuiteFingerprints {
|
|
|
55313
55377
|
constructor(scripts, always) {
|
|
55314
55378
|
this.scripts = scripts;
|
|
55315
55379
|
this.everywhere = ["res://project.godot", ...extensionsOf(scripts.projectPath)];
|
|
55316
|
-
this.alwaysDigest =
|
|
55380
|
+
this.alwaysDigest = createHash6("sha256").update(always.join(`
|
|
55317
55381
|
`)).digest("hex");
|
|
55318
55382
|
}
|
|
55319
55383
|
of(suite) {
|
|
@@ -55330,7 +55394,7 @@ class SuiteFingerprints {
|
|
|
55330
55394
|
}
|
|
55331
55395
|
pending.push(...reached.reaches);
|
|
55332
55396
|
}
|
|
55333
|
-
const hash =
|
|
55397
|
+
const hash = createHash6("sha256").update(this.alwaysDigest);
|
|
55334
55398
|
for (const path of [...seen].sort()) {
|
|
55335
55399
|
hash.update(`${path}
|
|
55336
55400
|
${this.read(path).digest}
|
|
@@ -55349,20 +55413,20 @@ ${this.read(path).digest}
|
|
|
55349
55413
|
return reached;
|
|
55350
55414
|
}
|
|
55351
55415
|
addon(root) {
|
|
55352
|
-
const hash =
|
|
55416
|
+
const hash = createHash6("sha256");
|
|
55353
55417
|
const walk = (directory, under) => {
|
|
55354
|
-
for (const entry of
|
|
55355
|
-
const file =
|
|
55418
|
+
for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
55419
|
+
const file = join20(directory, entry.name);
|
|
55356
55420
|
if (entry.isDirectory()) {
|
|
55357
55421
|
walk(file, `${under}${entry.name}/`);
|
|
55358
55422
|
} else if (entry.isFile()) {
|
|
55359
55423
|
hash.update(`${under}${entry.name}
|
|
55360
|
-
`).update(
|
|
55424
|
+
`).update(readFileSync15(file));
|
|
55361
55425
|
}
|
|
55362
55426
|
}
|
|
55363
55427
|
};
|
|
55364
|
-
const directory =
|
|
55365
|
-
if (
|
|
55428
|
+
const directory = join20(this.scripts.projectPath, root.slice("res://".length));
|
|
55429
|
+
if (existsSync17(directory)) {
|
|
55366
55430
|
walk(directory, "");
|
|
55367
55431
|
}
|
|
55368
55432
|
return { digest: hash.digest("hex"), reaches: [], unreadable: null };
|
|
@@ -55371,26 +55435,26 @@ ${this.read(path).digest}
|
|
|
55371
55435
|
if (PLACEHOLDER.test(path)) {
|
|
55372
55436
|
return { digest: "", reaches: [], unreadable: `${path} is filled in at run time` };
|
|
55373
55437
|
}
|
|
55374
|
-
const file =
|
|
55375
|
-
if (!
|
|
55438
|
+
const file = join20(this.scripts.projectPath, path.slice("res://".length));
|
|
55439
|
+
if (!existsSync17(file)) {
|
|
55376
55440
|
return { digest: "missing", reaches: [], unreadable: null };
|
|
55377
55441
|
}
|
|
55378
|
-
if (
|
|
55379
|
-
const inside =
|
|
55442
|
+
if (statSync11(file).isDirectory()) {
|
|
55443
|
+
const inside = readdirSync11(file, { withFileTypes: true }).filter((entry) => !entry.name.startsWith(".")).map((entry) => `${path.endsWith("/") ? path : `${path}/`}${entry.name}`).sort();
|
|
55380
55444
|
return { digest: inside.join(`
|
|
55381
55445
|
`), reaches: inside, unreadable: null };
|
|
55382
55446
|
}
|
|
55383
55447
|
if (BINARY_RESOURCE.test(path)) {
|
|
55384
55448
|
return { digest: "", reaches: [], unreadable: `${path} is a binary resource` };
|
|
55385
55449
|
}
|
|
55386
|
-
const bytes =
|
|
55387
|
-
const digest =
|
|
55450
|
+
const bytes = readFileSync15(file);
|
|
55451
|
+
const digest = createHash6("sha256").update(bytes).digest("hex");
|
|
55388
55452
|
const text = bytes.toString("latin1");
|
|
55389
55453
|
const reaches = new Set;
|
|
55390
55454
|
for (const [literal] of text.matchAll(RES_LITERAL)) {
|
|
55391
55455
|
reaches.add(literal);
|
|
55392
55456
|
}
|
|
55393
|
-
if (
|
|
55457
|
+
if (existsSync17(`${file}.import`)) {
|
|
55394
55458
|
reaches.add(`${path}.import`);
|
|
55395
55459
|
}
|
|
55396
55460
|
if (path.endsWith(".gd")) {
|
|
@@ -55420,8 +55484,8 @@ ${this.read(path).digest}
|
|
|
55420
55484
|
}
|
|
55421
55485
|
}
|
|
55422
55486
|
function extensionsOf(projectPath) {
|
|
55423
|
-
const list =
|
|
55424
|
-
return
|
|
55487
|
+
const list = join20(projectPath, ".godot", "extension_list.cfg");
|
|
55488
|
+
return existsSync17(list) ? readFileSync15(list, "utf8").split(`
|
|
55425
55489
|
`).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
|
|
55426
55490
|
}
|
|
55427
55491
|
|
|
@@ -55430,18 +55494,18 @@ init_suite_judge();
|
|
|
55430
55494
|
init_suite_plan();
|
|
55431
55495
|
|
|
55432
55496
|
// src/version.ts
|
|
55433
|
-
import { readFileSync as
|
|
55434
|
-
import { dirname as dirname11, join as
|
|
55497
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
55498
|
+
import { dirname as dirname11, join as join21 } from "node:path";
|
|
55435
55499
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
55436
55500
|
function getLocalVersion() {
|
|
55437
55501
|
const currentDirectory = dirname11(fileURLToPath2(import.meta.url));
|
|
55438
55502
|
const packageCandidates = [
|
|
55439
|
-
|
|
55440
|
-
|
|
55503
|
+
join21(currentDirectory, "..", "package.json"),
|
|
55504
|
+
join21(currentDirectory, "..", "..", "package.json")
|
|
55441
55505
|
];
|
|
55442
55506
|
for (const packagePath of packageCandidates) {
|
|
55443
55507
|
try {
|
|
55444
|
-
const value = JSON.parse(
|
|
55508
|
+
const value = JSON.parse(readFileSync18(packagePath, "utf-8"));
|
|
55445
55509
|
if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
|
|
55446
55510
|
return value.version;
|
|
55447
55511
|
}
|
|
@@ -55493,9 +55557,9 @@ function testOptionsOf(args, cwd) {
|
|
|
55493
55557
|
}
|
|
55494
55558
|
const first = positional[0];
|
|
55495
55559
|
const named = first === undefined ? null : resolve4(cwd, first);
|
|
55496
|
-
const projectNamed = named !== null &&
|
|
55560
|
+
const projectNamed = named !== null && existsSync19(join22(named, "project.godot"));
|
|
55497
55561
|
const projectPath = projectNamed ? named : resolve4(cwd);
|
|
55498
|
-
if (!
|
|
55562
|
+
if (!existsSync19(join22(projectPath, "project.godot"))) {
|
|
55499
55563
|
throw new TestUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
55500
55564
|
}
|
|
55501
55565
|
const last = (flag) => valued.get(flag)?.at(-1);
|
|
@@ -55539,7 +55603,7 @@ function resPathOf(entry, projectPath, cwd) {
|
|
|
55539
55603
|
const file = caseAt?.[1] ?? entry;
|
|
55540
55604
|
const fromProject = resolve4(projectPath, file);
|
|
55541
55605
|
const fromHere = resolve4(cwd, file);
|
|
55542
|
-
const absolute = isAbsolute4(file) || !
|
|
55606
|
+
const absolute = isAbsolute4(file) || !existsSync19(fromProject) && existsSync19(fromHere) ? fromHere : fromProject;
|
|
55543
55607
|
const inside = relative3(projectPath, absolute);
|
|
55544
55608
|
if (inside.startsWith("..") || isAbsolute4(inside)) {
|
|
55545
55609
|
throw new TestUsageError(`${entry} is not inside the project at ${projectPath}.`);
|
|
@@ -55551,7 +55615,7 @@ function homeOf(projectPath) {
|
|
|
55551
55615
|
return mainCheckoutOf(projectPath) ?? projectPath;
|
|
55552
55616
|
}
|
|
55553
55617
|
function selectedSuites(scripts, options) {
|
|
55554
|
-
const roots = options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) =>
|
|
55618
|
+
const roots = options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync19(join22(options.projectPath, root))).map((root) => `res://${root}`);
|
|
55555
55619
|
if (options.selected.length === 0) {
|
|
55556
55620
|
const found = scripts.suitesUnder(roots);
|
|
55557
55621
|
if (found.length === 0) {
|
|
@@ -55563,10 +55627,10 @@ function selectedSuites(scripts, options) {
|
|
|
55563
55627
|
for (const entry of options.selected) {
|
|
55564
55628
|
const caseAt = /^(res:\/\/.*\.gd):(\w+)$/.exec(entry);
|
|
55565
55629
|
const path = caseAt?.[1] ?? entry;
|
|
55566
|
-
const directory =
|
|
55567
|
-
const suites =
|
|
55630
|
+
const directory = join22(options.projectPath, path.slice(6));
|
|
55631
|
+
const suites = existsSync19(directory) && statSync12(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
|
|
55568
55632
|
if (suites.length === 0) {
|
|
55569
|
-
throw new TestUsageError(
|
|
55633
|
+
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
55634
|
}
|
|
55571
55635
|
for (const suite of suites) {
|
|
55572
55636
|
const picked = chosen.get(suite.path) ?? { suite, cases: new Set };
|
|
@@ -55643,17 +55707,17 @@ async function runTestCommand(options, godotPath) {
|
|
|
55643
55707
|
const startedAt = Date.now();
|
|
55644
55708
|
const scripts = new ProjectScripts(options.projectPath);
|
|
55645
55709
|
const home = homeOf(options.projectPath);
|
|
55646
|
-
const state =
|
|
55647
|
-
const timesFile =
|
|
55648
|
-
const passedFile =
|
|
55710
|
+
const state = join22(home, ".godot", "gdharness");
|
|
55711
|
+
const timesFile = join22(state, "suite-times.json");
|
|
55712
|
+
const passedFile = join22(state, "suite-passed.json");
|
|
55649
55713
|
let suites = selectedSuites(scripts, options);
|
|
55650
55714
|
const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
|
|
55651
55715
|
const declared = new Map(wholeSuites.map((suite) => [suite.path, suite.cases]));
|
|
55652
|
-
const testRuns =
|
|
55716
|
+
const testRuns = join22(options.projectPath, ".godot", "gdharness", "test-runs");
|
|
55653
55717
|
sweepRunDirectories(testRuns);
|
|
55654
|
-
const runDirectory =
|
|
55718
|
+
const runDirectory = join22(testRuns, `${String(startedAt)}-${String(process12.pid)}`);
|
|
55655
55719
|
mkdirSync8(runDirectory, { recursive: true });
|
|
55656
|
-
const slots = new EngineSlots(options.slotsDirectory ??
|
|
55720
|
+
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
55721
|
const running = new Set;
|
|
55658
55722
|
const interrupted = () => {
|
|
55659
55723
|
for (const end of running) {
|
|
@@ -55682,9 +55746,9 @@ async function runTestCommand(options, godotPath) {
|
|
|
55682
55746
|
}
|
|
55683
55747
|
const fingerprints = new SuiteFingerprints(scripts, [
|
|
55684
55748
|
getLocalVersion(),
|
|
55685
|
-
|
|
55749
|
+
readFileSync19(shippedListedRunner(), "utf8"),
|
|
55686
55750
|
...options.reachAlso.map((path) => `${path}
|
|
55687
|
-
${
|
|
55751
|
+
${existsSync19(path) ? readFileSync19(path, "latin1") : "missing"}`)
|
|
55688
55752
|
]);
|
|
55689
55753
|
const printsOf = new Map(suites.map((suite) => [suite.path, fingerprints.of(suite)]));
|
|
55690
55754
|
if (options.changedOnly) {
|
|
@@ -55709,7 +55773,7 @@ ${existsSync18(path) ? readFileSync18(path, "latin1") : "missing"}`)
|
|
|
55709
55773
|
const plans = options.order === null ? dealt(units, declared, room.engines) : [inOneEngine(ordered(units, options.order), declared)];
|
|
55710
55774
|
const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
|
|
55711
55775
|
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
|
|
55776
|
+
const how = options.order === null ? `${windowedSaid(plans)}${held}` : options.order === "reversed" ? ", in reverse order of path" : `, shuffled by ${String(options.order)}`;
|
|
55713
55777
|
console.log(`${plural(functions, "test function")} in ${plural(suites.length, "suite")} over ${plural(plans.length, "engine")}${how}.`);
|
|
55714
55778
|
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path).length > 1);
|
|
55715
55779
|
if (plans.length > 1 && split.length > 0) {
|
|
@@ -55737,7 +55801,7 @@ function plural(count, noun) {
|
|
|
55737
55801
|
}
|
|
55738
55802
|
function readRecord(file) {
|
|
55739
55803
|
try {
|
|
55740
|
-
const parsed = JSON.parse(
|
|
55804
|
+
const parsed = JSON.parse(readFileSync19(file, "utf8"));
|
|
55741
55805
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
55742
55806
|
} catch {
|
|
55743
55807
|
return {};
|
|
@@ -55764,20 +55828,20 @@ function recordPassed(file, ran, printsOf) {
|
|
|
55764
55828
|
writeJson(file, passed);
|
|
55765
55829
|
}
|
|
55766
55830
|
function sweepRunDirectories(testRuns) {
|
|
55767
|
-
if (!
|
|
55831
|
+
if (!existsSync19(testRuns)) {
|
|
55768
55832
|
return;
|
|
55769
55833
|
}
|
|
55770
|
-
for (const entry of
|
|
55834
|
+
for (const entry of readdirSync12(testRuns)) {
|
|
55771
55835
|
const pid = Number(/^\d+-(\d+)$/.exec(entry)?.[1]);
|
|
55772
55836
|
if (!Number.isInteger(pid) || !alive(pid)) {
|
|
55773
|
-
rmSync10(
|
|
55837
|
+
rmSync10(join22(testRuns, entry), { recursive: true, force: true });
|
|
55774
55838
|
}
|
|
55775
55839
|
}
|
|
55776
55840
|
}
|
|
55777
55841
|
async function importProject(launch) {
|
|
55778
|
-
const directory =
|
|
55779
|
-
mkdirSync8(
|
|
55780
|
-
const log =
|
|
55842
|
+
const directory = join22(launch.runDirectory, "import");
|
|
55843
|
+
mkdirSync8(join22(directory, "user"), { recursive: true });
|
|
55844
|
+
const log = join22(directory, "import.log");
|
|
55781
55845
|
const lock = await launch.slots.take();
|
|
55782
55846
|
try {
|
|
55783
55847
|
const ended = await watched(launch, ["--headless", "--audio-driver", "Dummy", "--path", launch.projectPath, "--import"], directory, log, false, launch.silenceFloorSeconds);
|
|
@@ -55790,11 +55854,11 @@ async function importProject(launch) {
|
|
|
55790
55854
|
}
|
|
55791
55855
|
}
|
|
55792
55856
|
async function runEngine2(launch, plan, index, units, again = "") {
|
|
55793
|
-
const directory =
|
|
55794
|
-
mkdirSync8(
|
|
55795
|
-
const suitesFile =
|
|
55796
|
-
const skippingFile =
|
|
55797
|
-
const expectedFile =
|
|
55857
|
+
const directory = join22(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
|
|
55858
|
+
mkdirSync8(join22(directory, "user"), { recursive: true });
|
|
55859
|
+
const suitesFile = join22(directory, "suites.txt");
|
|
55860
|
+
const skippingFile = join22(directory, "skipping.txt");
|
|
55861
|
+
const expectedFile = join22(directory, "expected.json");
|
|
55798
55862
|
writeFileSync10(suitesFile, `${plan.suites.map((suite) => suite.path).join(`
|
|
55799
55863
|
`)}
|
|
55800
55864
|
`);
|
|
@@ -55814,7 +55878,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55814
55878
|
...plan.windowed ? [] : ["--ignoreHeadlessMode"],
|
|
55815
55879
|
"-c",
|
|
55816
55880
|
"-rd",
|
|
55817
|
-
`res://${relative3(launch.projectPath,
|
|
55881
|
+
`res://${relative3(launch.projectPath, join22(directory, "reports")).split(sep2).join("/")}`,
|
|
55818
55882
|
"-rc",
|
|
55819
55883
|
"1",
|
|
55820
55884
|
"--",
|
|
@@ -55831,7 +55895,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55831
55895
|
const held = new Set(plan.suites.map((suite) => suite.path));
|
|
55832
55896
|
const slowest = Math.max(0, ...units.filter((unit) => held.has(unit.suite)).map((unit) => unit.cost));
|
|
55833
55897
|
const silence = Math.max(launch.silenceFloorSeconds, SILENCE_TIMES_SLOWEST * slowest);
|
|
55834
|
-
const log =
|
|
55898
|
+
const log = join22(directory, "engine.log");
|
|
55835
55899
|
const lock = await launch.slots.take();
|
|
55836
55900
|
const began = Date.now();
|
|
55837
55901
|
let end;
|
|
@@ -55840,7 +55904,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55840
55904
|
} finally {
|
|
55841
55905
|
launch.slots.release(lock);
|
|
55842
55906
|
}
|
|
55843
|
-
const printed = readRun(
|
|
55907
|
+
const printed = readRun(readFileSync19(log, "utf8"));
|
|
55844
55908
|
const assignment = { suites: plan.suites, expected: readExpected(expectedFile) };
|
|
55845
55909
|
return {
|
|
55846
55910
|
index,
|
|
@@ -55852,7 +55916,7 @@ async function runEngine2(launch, plan, index, units, again = "") {
|
|
|
55852
55916
|
};
|
|
55853
55917
|
}
|
|
55854
55918
|
async function watched(launch, args, directory, log, windowed, silenceSeconds) {
|
|
55855
|
-
const user =
|
|
55919
|
+
const user = join22(directory, "user");
|
|
55856
55920
|
const env = { ...process12.env, APPDATA: user, XDG_DATA_HOME: user };
|
|
55857
55921
|
const output = openSync3(log, "w");
|
|
55858
55922
|
let child;
|
|
@@ -55864,12 +55928,12 @@ async function watched(launch, args, directory, log, windowed, silenceSeconds) {
|
|
|
55864
55928
|
const how = windowed ? "on a desktop of its own" : "through the desktop helper";
|
|
55865
55929
|
return { code: null, signal: null, ended: `the engine could not be started ${how}: ${compiled}` };
|
|
55866
55930
|
}
|
|
55867
|
-
const stopFile =
|
|
55931
|
+
const stopFile = join22(directory, "stop");
|
|
55868
55932
|
const how = windowed ? { desktop: HIDDEN_DESKTOP, directory: launch.projectPath } : { directory: launch.projectPath };
|
|
55869
55933
|
const wrapper = throughHelper(how, launch.godotPath, args, {
|
|
55870
|
-
spec:
|
|
55871
|
-
pid:
|
|
55872
|
-
error:
|
|
55934
|
+
spec: join22(directory, "spec.json"),
|
|
55935
|
+
pid: join22(directory, "pid"),
|
|
55936
|
+
error: join22(directory, "error"),
|
|
55873
55937
|
stop: stopFile
|
|
55874
55938
|
});
|
|
55875
55939
|
child = spawnHidden(wrapper.command, wrapper.args, {
|