gdharness 1.2.27 → 1.2.28

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/index.js CHANGED
@@ -3363,17 +3363,17 @@ import {
3363
3363
  closeSync as closeSync2,
3364
3364
  constants,
3365
3365
  copyFileSync,
3366
- existsSync as existsSync17,
3366
+ existsSync as existsSync18,
3367
3367
  fstatSync,
3368
3368
  mkdirSync as mkdirSync14,
3369
3369
  openSync as openSync3,
3370
- readdirSync as readdirSync9,
3371
- readFileSync as readFileSync21,
3370
+ readdirSync as readdirSync10,
3371
+ readFileSync as readFileSync22,
3372
3372
  readSync as readSync2,
3373
3373
  rmdirSync as rmdirSync2,
3374
3374
  statSync as statSync9
3375
3375
  } from "node:fs";
3376
- import { basename as basename5, dirname as dirname16, extname as extname3, isAbsolute as isAbsolute3, join as join23, normalize as normalize2, relative as relative4 } from "node:path";
3376
+ import { basename as basename6, dirname as dirname17, extname as extname3, isAbsolute as isAbsolute4, join as join24, normalize as normalize2, relative as relative4 } from "node:path";
3377
3377
  import { setTimeout as delay4 } from "node:timers/promises";
3378
3378
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3379
3379
  import { promisify as promisify5 } from "node:util";
@@ -29517,33 +29517,114 @@ class GodotLocator {
29517
29517
  }
29518
29518
 
29519
29519
  // src/headless.ts
29520
- import { existsSync as existsSync11, readFileSync as readFileSync13, realpathSync as realpathSync2, writeFileSync as writeFileSync9 } from "node:fs";
29521
- import { dirname as dirname8, join as join14 } from "node:path";
29520
+ import { existsSync as existsSync12, readFileSync as readFileSync14, realpathSync as realpathSync2, writeFileSync as writeFileSync9 } from "node:fs";
29521
+ import { dirname as dirname9, join as join15 } from "node:path";
29522
+
29523
+ // src/library-copies.ts
29524
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync12, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
29525
+ import { basename as basename2, dirname as dirname8, isAbsolute as isAbsolute3, join as join12 } from "node:path";
29526
+ function freeLibraryCopies(projectPath, platform = process.platform) {
29527
+ const setAside = [];
29528
+ const swept = [];
29529
+ if (platform !== "win32") {
29530
+ return { setAside, swept };
29531
+ }
29532
+ for (const library of windowsLibraries(projectPath)) {
29533
+ const folder = dirname8(library);
29534
+ const copy = `~${basename2(library)}`;
29535
+ if (existsSync9(join12(folder, copy))) {
29536
+ const aside = join12(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
29537
+ try {
29538
+ renameSync(join12(folder, copy), aside);
29539
+ setAside.push(aside);
29540
+ } catch {}
29541
+ }
29542
+ let names;
29543
+ try {
29544
+ names = readdirSync4(folder);
29545
+ } catch {
29546
+ continue;
29547
+ }
29548
+ for (const name of names) {
29549
+ if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
29550
+ try {
29551
+ unlinkSync2(join12(folder, name));
29552
+ swept.push(join12(folder, name));
29553
+ } catch {}
29554
+ }
29555
+ }
29556
+ }
29557
+ return { setAside, swept };
29558
+ }
29559
+ function windowsLibraries(projectPath) {
29560
+ const listed = readOrNull(join12(projectPath, ".godot", "extension_list.cfg"));
29561
+ if (listed === null) {
29562
+ return [];
29563
+ }
29564
+ const libraries = new Set;
29565
+ for (const line of listed.split(/\r?\n/)) {
29566
+ const manifest = resolveResource(projectPath, projectPath, line.trim());
29567
+ const text = manifest === null ? null : readOrNull(manifest);
29568
+ if (manifest === null || text === null) {
29569
+ continue;
29570
+ }
29571
+ let inLibraries = false;
29572
+ for (const row of text.split(/\r?\n/)) {
29573
+ const section = /^\s*\[([^\]]+)\]/.exec(row)?.[1];
29574
+ if (section !== undefined) {
29575
+ inLibraries = section.trim() === "libraries";
29576
+ continue;
29577
+ }
29578
+ const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
29579
+ const library = named === undefined ? null : resolveResource(projectPath, dirname8(manifest), named);
29580
+ if (library !== null) {
29581
+ libraries.add(library);
29582
+ }
29583
+ }
29584
+ }
29585
+ return [...libraries];
29586
+ }
29587
+ function resolveResource(projectPath, relativeTo, path) {
29588
+ if (path === "") {
29589
+ return null;
29590
+ }
29591
+ if (path.startsWith("res://")) {
29592
+ return join12(projectPath, path.slice("res://".length));
29593
+ }
29594
+ return isAbsolute3(path) ? path : join12(relativeTo, path);
29595
+ }
29596
+ function readOrNull(path) {
29597
+ try {
29598
+ return readFileSync12(path, "utf8");
29599
+ } catch {
29600
+ return null;
29601
+ }
29602
+ }
29522
29603
 
29523
29604
  // src/reimport.ts
29524
29605
  import { createHash as createHash2 } from "node:crypto";
29525
- import { existsSync as existsSync9, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync5, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
29526
- import { basename as basename2, join as join12 } from "node:path";
29606
+ import { existsSync as existsSync10, readFileSync as readFileSync13, rmSync as rmSync5, statSync as statSync5, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
29607
+ import { basename as basename3, join as join13 } from "node:path";
29527
29608
  function forceNextImport(projectPath, resourcePath) {
29528
29609
  const relative = resourcePath.replace(/^res:\/\//, "");
29529
- const sidecar = join12(projectPath, `${relative}.import`);
29530
- if (!existsSync9(sidecar)) {
29610
+ const sidecar = join13(projectPath, `${relative}.import`);
29611
+ if (!existsSync10(sidecar)) {
29531
29612
  return;
29532
29613
  }
29533
29614
  const { atime, mtimeMs } = statSync5(sidecar);
29534
- writeFileSync8(sidecar, readFileSync12(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
29615
+ writeFileSync8(sidecar, readFileSync13(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
29535
29616
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
29536
29617
  rmSync5(importRecord(projectPath, resourcePath), { force: true });
29537
29618
  }
29538
29619
  function importRecord(projectPath, resourcePath) {
29539
29620
  const digest = createHash2("md5").update(resourcePath).digest("hex");
29540
- return join12(projectPath, ".godot", "imported", `${basename2(resourcePath)}-${digest}.md5`);
29621
+ return join13(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
29541
29622
  }
29542
29623
 
29543
29624
  // src/scratch.ts
29544
- import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync6 } from "node:fs";
29625
+ import { existsSync as existsSync11, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync6 } from "node:fs";
29545
29626
  import { tmpdir as tmpdir4 } from "node:os";
29546
- import { join as join13 } from "node:path";
29627
+ import { join as join14 } from "node:path";
29547
29628
  import process7 from "node:process";
29548
29629
  var SWEPT = { recursive: true, force: true };
29549
29630
  var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
@@ -29558,12 +29639,12 @@ var KINDS = [
29558
29639
  "tests"
29559
29640
  ];
29560
29641
  function scratchDirectory(kind) {
29561
- return mkdtempSync(join13(tmpdir4(), `gdharness-${kind}-${process7.pid}-`));
29642
+ return mkdtempSync(join14(tmpdir4(), `gdharness-${kind}-${process7.pid}-`));
29562
29643
  }
29563
29644
  function sweepAbandonedScratch(directory = tmpdir4()) {
29564
29645
  let entries;
29565
29646
  try {
29566
- entries = readdirSync4(directory);
29647
+ entries = readdirSync5(directory);
29567
29648
  } catch {
29568
29649
  return;
29569
29650
  }
@@ -29574,7 +29655,7 @@ function sweepAbandonedScratch(directory = tmpdir4()) {
29574
29655
  continue;
29575
29656
  }
29576
29657
  try {
29577
- remove(join13(directory, entry));
29658
+ remove(join14(directory, entry));
29578
29659
  } catch {}
29579
29660
  }
29580
29661
  }
@@ -29612,7 +29693,7 @@ function tryAgain(path, attempt) {
29612
29693
  lastTryOnExit();
29613
29694
  }
29614
29695
  const after = AGAIN_AFTER_MS[attempt];
29615
- if (after === undefined || !existsSync10(path)) {
29696
+ if (after === undefined || !existsSync11(path)) {
29616
29697
  waiting.delete(path);
29617
29698
  if (after === undefined) {
29618
29699
  atExit.add(path);
@@ -29703,7 +29784,7 @@ function refusalsIn(log) {
29703
29784
  }
29704
29785
  function scriptFault(problems, script) {
29705
29786
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
29706
- const directories = [...new Set([spelled(dirname8(script)), spelled(resolvedOr(dirname8(script)))])];
29787
+ const directories = [...new Set([spelled(dirname9(script)), spelled(resolvedOr(dirname9(script)))])];
29707
29788
  return problems.find((entry) => {
29708
29789
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
29709
29790
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
@@ -29728,7 +29809,7 @@ function librariesNotCopied(messages) {
29728
29809
  }
29729
29810
  function extensionRetryNote(libraries, stillNotLoaded = []) {
29730
29811
  const second = stillNotLoaded.length === 0 ? "so the import was run again and loaded the extension, and the messages are from that second run." : `so the import was run again, and it still could not load ${stillNotLoaded.join(" and ")}: this answer is from an import without ${stillNotLoaded.length === 1 ? "it" : "them"}, and its messages say what the engine hit.`;
29731
- return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "can be deleted once the editor has exited.";
29812
+ return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "the next import, export or editor this server starts on the project deletes once the editor has exited.";
29732
29813
  }
29733
29814
  async function runImport(godotPath, projectPath, options = {}) {
29734
29815
  const once = options.once ?? importOnce;
@@ -29736,6 +29817,7 @@ async function runImport(godotPath, projectPath, options = {}) {
29736
29817
  for (const target of options.forcing ?? []) {
29737
29818
  forceNextImport(projectPath, target);
29738
29819
  }
29820
+ freeLibraryCopies(projectPath);
29739
29821
  return once(godotPath, projectPath);
29740
29822
  };
29741
29823
  const first = await pass();
@@ -29759,7 +29841,7 @@ async function importOnce(godotPath, projectPath) {
29759
29841
  ran = await runEngine(godotPath, [
29760
29842
  "--headless",
29761
29843
  "--log-file",
29762
- join14(logDir, "engine.log"),
29844
+ join15(logDir, "engine.log"),
29763
29845
  "--path",
29764
29846
  projectPath,
29765
29847
  "--import"
@@ -29779,13 +29861,13 @@ async function importOnce(godotPath, projectPath) {
29779
29861
  }
29780
29862
  async function runOperation(engine, operation, params, projectPath) {
29781
29863
  const paramsDir = scratchDirectory("params");
29782
- const paramsFile = join14(paramsDir, `${operation}.json`);
29783
- const answerFile = join14(paramsDir, "answer.json");
29864
+ const paramsFile = join15(paramsDir, `${operation}.json`);
29865
+ const answerFile = join15(paramsDir, "answer.json");
29784
29866
  writeFileSync9(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
29785
29867
  const args = [
29786
29868
  "--headless",
29787
29869
  "--log-file",
29788
- join14(paramsDir, "engine.log"),
29870
+ join15(paramsDir, "engine.log"),
29789
29871
  "--path",
29790
29872
  projectPath,
29791
29873
  "--script",
@@ -29855,10 +29937,10 @@ async function runOperation(engine, operation, params, projectPath) {
29855
29937
  };
29856
29938
  }
29857
29939
  function answerIn(file) {
29858
- if (!existsSync11(file)) {
29940
+ if (!existsSync12(file)) {
29859
29941
  return { kind: "none" };
29860
29942
  }
29861
- const text = readFileSync13(file, "utf8");
29943
+ const text = readFileSync14(file, "utf8");
29862
29944
  try {
29863
29945
  const parsed = JSON.parse(text);
29864
29946
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
@@ -30431,20 +30513,20 @@ function askTheKeeper(address, line, withinMs) {
30431
30513
  }
30432
30514
 
30433
30515
  // src/lifecycle-log.ts
30434
- import { appendFileSync, mkdirSync as mkdirSync8, renameSync, statSync as statSync6 } from "node:fs";
30435
- import { dirname as dirname9, join as join15 } from "node:path";
30516
+ import { appendFileSync, mkdirSync as mkdirSync8, renameSync as renameSync2, statSync as statSync6 } from "node:fs";
30517
+ import { dirname as dirname10, join as join16 } from "node:path";
30436
30518
  import process8 from "node:process";
30437
30519
  function lifecycleLogPath() {
30438
- return join15(runtimeDirectory(), "servers.log");
30520
+ return join16(runtimeDirectory(), "servers.log");
30439
30521
  }
30440
30522
  var LIFECYCLE_LOG_LIMIT = 1e6;
30441
30523
  function writeLifecycle(event, fields = {}) {
30442
30524
  const path = lifecycleLogPath();
30443
30525
  try {
30444
- mkdirSync8(dirname9(path), { recursive: true });
30526
+ mkdirSync8(dirname10(path), { recursive: true });
30445
30527
  if ((statSync6(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
30446
30528
  try {
30447
- renameSync(path, `${path}.1`);
30529
+ renameSync2(path, `${path}.1`);
30448
30530
  } catch {}
30449
30531
  }
30450
30532
  const line = {
@@ -30474,10 +30556,10 @@ function callsInFlight(calls, now = Date.now()) {
30474
30556
  }
30475
30557
 
30476
30558
  // src/lsp_client.ts
30477
- import { existsSync as existsSync12, realpathSync as realpathSync3 } from "node:fs";
30559
+ import { existsSync as existsSync13, realpathSync as realpathSync3 } from "node:fs";
30478
30560
  import { readFile, realpath } from "node:fs/promises";
30479
30561
  import { createConnection as createConnection3 } from "node:net";
30480
- import { dirname as dirname10, join as join16, resolve as resolve3 } from "node:path";
30562
+ import { dirname as dirname11, join as join17, resolve as resolve3 } from "node:path";
30481
30563
  import { setTimeout as delay3 } from "node:timers/promises";
30482
30564
  import { fileURLToPath, pathToFileURL } from "node:url";
30483
30565
  var DEFAULT_LSP_PORT = 6005;
@@ -30675,7 +30757,7 @@ class GodotLSPClient {
30675
30757
  if (this.initialized) {
30676
30758
  return;
30677
30759
  }
30678
- const rootPath = this.rootPath ?? dirname10(resolve3(filePath));
30760
+ const rootPath = this.rootPath ?? dirname11(resolve3(filePath));
30679
30761
  await this.initialize(rootPath);
30680
30762
  }
30681
30763
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -31233,7 +31315,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
31233
31315
  } catch {
31234
31316
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
31235
31317
  }
31236
- if (!existsSync12(join16(projectPath, "project.godot"))) {
31318
+ if (!existsSync13(join17(projectPath, "project.godot"))) {
31237
31319
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
31238
31320
  }
31239
31321
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -31440,8 +31522,8 @@ function secondsFromClock(printed) {
31440
31522
  }
31441
31523
 
31442
31524
  // src/project-scan.ts
31443
- import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "node:fs";
31444
- import { extname, join as join17 } from "node:path";
31525
+ import { existsSync as existsSync14, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "node:fs";
31526
+ import { extname, join as join18 } from "node:path";
31445
31527
  var SKIPPED = new Set(["node_modules"]);
31446
31528
  function skipped(name) {
31447
31529
  return name.startsWith(".") || SKIPPED.has(name);
@@ -31451,10 +31533,10 @@ function steppedOver(directory) {
31451
31533
  return gdignored(directory) || installedByUs(directory);
31452
31534
  }
31453
31535
  function gdignored(directory) {
31454
- return existsSync13(join17(directory, ".gdignore"));
31536
+ return existsSync14(join18(directory, ".gdignore"));
31455
31537
  }
31456
31538
  function installedByUs(directory) {
31457
- return existsSync13(join17(directory, VERSION_MARKER));
31539
+ return existsSync14(join18(directory, VERSION_MARKER));
31458
31540
  }
31459
31541
  var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
31460
31542
  var ASSET_EXTENSIONS = new Set([
@@ -31493,12 +31575,12 @@ function projectStructure(projectPath) {
31493
31575
  if (steppedOver(directory)) {
31494
31576
  return;
31495
31577
  }
31496
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
31578
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
31497
31579
  if (skipped(entry.name)) {
31498
31580
  continue;
31499
31581
  }
31500
31582
  if (entry.isDirectory()) {
31501
- visit(join17(directory, entry.name));
31583
+ visit(join18(directory, entry.name));
31502
31584
  } else if (entry.isFile()) {
31503
31585
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
31504
31586
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -31522,13 +31604,13 @@ function projectFiles(projectPath, extensions) {
31522
31604
  if (steppedOver(directory)) {
31523
31605
  return;
31524
31606
  }
31525
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
31607
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
31526
31608
  if (skipped(entry.name)) {
31527
31609
  continue;
31528
31610
  }
31529
31611
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
31530
31612
  if (entry.isDirectory()) {
31531
- visit(join17(directory, entry.name), spelled);
31613
+ visit(join18(directory, entry.name), spelled);
31532
31614
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
31533
31615
  found.push(spelled);
31534
31616
  }
@@ -31544,20 +31626,20 @@ function scriptsWithoutUid(projectPath) {
31544
31626
  if (steppedOver(directory)) {
31545
31627
  return;
31546
31628
  }
31547
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
31629
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
31548
31630
  if (skipped(entry.name)) {
31549
31631
  continue;
31550
31632
  }
31551
31633
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
31552
31634
  if (entry.isDirectory()) {
31553
- visit(join17(directory, entry.name), spelled);
31635
+ visit(join18(directory, entry.name), spelled);
31554
31636
  continue;
31555
31637
  }
31556
31638
  if (!entry.isFile()) {
31557
31639
  continue;
31558
31640
  }
31559
31641
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
31560
- if (CARRIES_A_UID.has(extension) && !existsSync13(join17(directory, `${entry.name}.uid`))) {
31642
+ if (CARRIES_A_UID.has(extension) && !existsSync14(join18(directory, `${entry.name}.uid`))) {
31561
31643
  found.push(spelled);
31562
31644
  }
31563
31645
  }
@@ -31585,11 +31667,11 @@ function projectTexts(projectPath) {
31585
31667
  if (steppedOver(directory)) {
31586
31668
  return;
31587
31669
  }
31588
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
31670
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
31589
31671
  if (skipped(entry.name)) {
31590
31672
  continue;
31591
31673
  }
31592
- const absolute = join17(directory, entry.name);
31674
+ const absolute = join18(directory, entry.name);
31593
31675
  const spelled = `${prefix}${entry.name}`;
31594
31676
  if (entry.isDirectory()) {
31595
31677
  visit(absolute, `${spelled}/`);
@@ -31599,7 +31681,7 @@ function projectTexts(projectPath) {
31599
31681
  if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
31600
31682
  continue;
31601
31683
  }
31602
- const bytes = readFileSync14(absolute);
31684
+ const bytes = readFileSync15(absolute);
31603
31685
  if (!isBinary(bytes)) {
31604
31686
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
31605
31687
  }
@@ -31615,11 +31697,11 @@ function ignoredTexts(projectPath) {
31615
31697
  return;
31616
31698
  }
31617
31699
  const under = ignored || gdignored(directory);
31618
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
31700
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
31619
31701
  if (skipped(entry.name)) {
31620
31702
  continue;
31621
31703
  }
31622
- const absolute = join17(directory, entry.name);
31704
+ const absolute = join18(directory, entry.name);
31623
31705
  const spelled = `${prefix}${entry.name}`;
31624
31706
  if (entry.isDirectory()) {
31625
31707
  visit(absolute, `${spelled}/`, under);
@@ -31629,7 +31711,7 @@ function ignoredTexts(projectPath) {
31629
31711
  if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
31630
31712
  continue;
31631
31713
  }
31632
- const bytes = readFileSync14(absolute);
31714
+ const bytes = readFileSync15(absolute);
31633
31715
  if (!isBinary(bytes)) {
31634
31716
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
31635
31717
  }
@@ -31652,14 +31734,14 @@ function searchProject(projectPath, options) {
31652
31734
  if (steppedOver(directory)) {
31653
31735
  return;
31654
31736
  }
31655
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
31737
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
31656
31738
  if (full()) {
31657
31739
  return;
31658
31740
  }
31659
31741
  if (skipped(entry.name)) {
31660
31742
  continue;
31661
31743
  }
31662
- const entryPath = join17(directory, entry.name);
31744
+ const entryPath = join18(directory, entry.name);
31663
31745
  if (entry.isDirectory()) {
31664
31746
  visit(entryPath);
31665
31747
  continue;
@@ -31668,7 +31750,7 @@ function searchProject(projectPath, options) {
31668
31750
  if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
31669
31751
  continue;
31670
31752
  }
31671
- const bytes = readFileSync14(entryPath);
31753
+ const bytes = readFileSync15(entryPath);
31672
31754
  if (extensions === null && isBinary(bytes)) {
31673
31755
  continue;
31674
31756
  }
@@ -31830,8 +31912,8 @@ function setupPromptHandlers(server) {
31830
31912
  }
31831
31913
 
31832
31914
  // src/rename.ts
31833
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync15, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
31834
- import { dirname as dirname11, posix as posix2 } from "node:path";
31915
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync16, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
31916
+ import { dirname as dirname12, posix as posix2 } from "node:path";
31835
31917
  class Script {
31836
31918
  path;
31837
31919
  text;
@@ -32553,26 +32635,26 @@ function rewritesOf(plan, texts, move) {
32553
32635
  }
32554
32636
  function applyRewrites(rewrites) {
32555
32637
  for (const rewrite of rewrites) {
32556
- const now = readFileSync15(rewrite.absolute, "utf8");
32638
+ const now = readFileSync16(rewrite.absolute, "utf8");
32557
32639
  if (now !== rewrite.before) {
32558
32640
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
32559
32641
  }
32560
- if (rewrite.writeTo !== rewrite.absolute && existsSync14(rewrite.writeTo)) {
32642
+ if (rewrite.writeTo !== rewrite.absolute && existsSync15(rewrite.writeTo)) {
32561
32643
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
32562
32644
  }
32563
32645
  }
32564
32646
  const done = [];
32565
32647
  try {
32566
32648
  for (const rewrite of rewrites) {
32567
- mkdirSync9(dirname11(rewrite.writeTo), { recursive: true });
32649
+ mkdirSync9(dirname12(rewrite.writeTo), { recursive: true });
32568
32650
  const staging = `${rewrite.writeTo}.gdharness-rename`;
32569
32651
  writeFileSync10(staging, rewrite.after, "utf8");
32570
- renameSync2(staging, rewrite.writeTo);
32652
+ renameSync3(staging, rewrite.writeTo);
32571
32653
  let movedUid = false;
32572
32654
  if (rewrite.writeTo !== rewrite.absolute) {
32573
- unlinkSync2(rewrite.absolute);
32574
- if (existsSync14(`${rewrite.absolute}.uid`)) {
32575
- renameSync2(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
32655
+ unlinkSync3(rewrite.absolute);
32656
+ if (existsSync15(`${rewrite.absolute}.uid`)) {
32657
+ renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
32576
32658
  movedUid = true;
32577
32659
  }
32578
32660
  }
@@ -32583,9 +32665,9 @@ function applyRewrites(rewrites) {
32583
32665
  try {
32584
32666
  writeFileSync10(rewrite.absolute, rewrite.before, "utf8");
32585
32667
  if (rewrite.writeTo !== rewrite.absolute) {
32586
- unlinkSync2(rewrite.writeTo);
32668
+ unlinkSync3(rewrite.writeTo);
32587
32669
  if (movedUid) {
32588
- renameSync2(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
32670
+ renameSync3(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
32589
32671
  }
32590
32672
  }
32591
32673
  } catch {}
@@ -32595,11 +32677,11 @@ function applyRewrites(rewrites) {
32595
32677
  }
32596
32678
 
32597
32679
  // src/rename-symbol.ts
32598
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync6, realpathSync as realpathSync5, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
32599
- import { basename as basename3, dirname as dirname12, join as join18, relative as relative3, resolve as resolve5 } from "node:path";
32680
+ import { existsSync as existsSync16, mkdirSync as mkdirSync10, readdirSync as readdirSync7, realpathSync as realpathSync5, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
32681
+ import { basename as basename4, dirname as dirname13, join as join19, relative as relative3, resolve as resolve5 } from "node:path";
32600
32682
 
32601
32683
  // src/resources.ts
32602
- import { readFileSync as readFileSync16, realpathSync as realpathSync4 } from "node:fs";
32684
+ import { readFileSync as readFileSync17, realpathSync as realpathSync4 } from "node:fs";
32603
32685
  import { extname as extname2, resolve as resolve4 } from "node:path";
32604
32686
  var STATIC_RESOURCES = [
32605
32687
  {
@@ -32830,7 +32912,7 @@ function readResourceText(uri, getProjectPath) {
32830
32912
  const parsedUri = parseGodotUri(uri);
32831
32913
  if (parsedUri.kind === "project-info") {
32832
32914
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
32833
- const rawProject = readFileSync16(projectFilePath, "utf-8");
32915
+ const rawProject = readFileSync17(projectFilePath, "utf-8");
32834
32916
  const parsedProject = parseProjectGodot(rawProject);
32835
32917
  return {
32836
32918
  mimeType: "application/json",
@@ -32839,7 +32921,7 @@ function readResourceText(uri, getProjectPath) {
32839
32921
  }
32840
32922
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
32841
32923
  ensureAllowedExtension(parsedUri.kind, filePath);
32842
- const text = readFileSync16(filePath, "utf-8");
32924
+ const text = readFileSync17(filePath, "utf-8");
32843
32925
  return {
32844
32926
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
32845
32927
  text
@@ -32904,13 +32986,13 @@ function onDiskSpelling(path) {
32904
32986
  let at = resolve5(path);
32905
32987
  for (;; ) {
32906
32988
  try {
32907
- return join18(realpathSync5.native(at), ...missing.reverse());
32989
+ return join19(realpathSync5.native(at), ...missing.reverse());
32908
32990
  } catch {
32909
- const parent = dirname12(at);
32991
+ const parent = dirname13(at);
32910
32992
  if (parent === at) {
32911
32993
  return resolve5(path);
32912
32994
  }
32913
- missing.push(basename3(at));
32995
+ missing.push(basename4(at));
32914
32996
  at = parent;
32915
32997
  }
32916
32998
  }
@@ -32957,7 +33039,7 @@ async function renameSymbol(request, services) {
32957
33039
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
32958
33040
  };
32959
33041
  }
32960
- if (existsSync15(request.newScriptPath)) {
33042
+ if (existsSync16(request.newScriptPath)) {
32961
33043
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
32962
33044
  }
32963
33045
  }
@@ -33133,7 +33215,7 @@ async function renameSymbol(request, services) {
33133
33215
  scripts,
33134
33216
  from: declaringPath,
33135
33217
  to: moveTo,
33136
- exists: (path) => existsSync15(`${projectPath}/${path.slice("res://".length)}`)
33218
+ exists: (path) => existsSync16(`${projectPath}/${path.slice("res://".length)}`)
33137
33219
  }, plan);
33138
33220
  }
33139
33221
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? null : declaringPath);
@@ -33334,15 +33416,15 @@ ${mention.kind}`;
33334
33416
  var LEFT_ALONE_ANSWER_LINES = 300;
33335
33417
  var detailWrites = 0;
33336
33418
  function writeLeftAloneDetail(projectPath, symbol, detail) {
33337
- const directory = join18(projectPath, ".godot", "gdharness-renames");
33419
+ const directory = join19(projectPath, ".godot", "gdharness-renames");
33338
33420
  mkdirSync10(directory, { recursive: true });
33339
33421
  detailWrites += 1;
33340
- const path = join18(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
33422
+ const path = join19(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
33341
33423
  writeFileSync11(path, `${JSON.stringify(detail, null, 2)}
33342
33424
  `, "utf8");
33343
- const kept = readdirSync6(directory).filter((name) => name.endsWith(".json")).sort().reverse();
33425
+ const kept = readdirSync7(directory).filter((name) => name.endsWith(".json")).sort().reverse();
33344
33426
  for (const old of kept.slice(10)) {
33345
- rmSync7(join18(directory, old), { force: true });
33427
+ rmSync7(join19(directory, old), { force: true });
33346
33428
  }
33347
33429
  return path;
33348
33430
  }
@@ -33365,15 +33447,15 @@ function summaryOf(edits, mentions, rewrites, left) {
33365
33447
  }
33366
33448
 
33367
33449
  // src/restart-note.ts
33368
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync17, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "node:fs";
33369
- import { dirname as dirname13, join as join19 } from "node:path";
33450
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "node:fs";
33451
+ import { dirname as dirname14, join as join20 } from "node:path";
33370
33452
  function restartNotePath(projectPath) {
33371
- return join19(projectPath, ".godot", "gdharness-restart.json");
33453
+ return join20(projectPath, ".godot", "gdharness-restart.json");
33372
33454
  }
33373
33455
  function noteRestartBegun(note) {
33374
33456
  const path = restartNotePath(note.projectPath);
33375
33457
  try {
33376
- mkdirSync11(dirname13(path), { recursive: true });
33458
+ mkdirSync11(dirname14(path), { recursive: true });
33377
33459
  writeFileSync12(path, `${JSON.stringify(note, null, 2)}
33378
33460
  `, "utf8");
33379
33461
  } catch (error) {
@@ -33382,7 +33464,7 @@ function noteRestartBegun(note) {
33382
33464
  }
33383
33465
  function restartOwed(projectPath) {
33384
33466
  try {
33385
- const parsed = JSON.parse(readFileSync17(restartNotePath(projectPath), "utf8"));
33467
+ const parsed = JSON.parse(readFileSync18(restartNotePath(projectPath), "utf8"));
33386
33468
  if (typeof parsed !== "object" || parsed === null) {
33387
33469
  return null;
33388
33470
  }
@@ -33406,12 +33488,12 @@ function restartSettled(projectPath) {
33406
33488
  // src/run-record.ts
33407
33489
  import { execFile as execFile4 } from "node:child_process";
33408
33490
  import { createHash as createHash3 } from "node:crypto";
33409
- import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync18, realpathSync as realpathSync6, rmSync as rmSync10, statSync as statSync7 } from "node:fs";
33410
- import { basename as basename4, dirname as dirname14, join as join20, resolve as resolve6 } from "node:path";
33491
+ import { mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, rmSync as rmSync10, statSync as statSync7 } from "node:fs";
33492
+ import { basename as basename5, dirname as dirname15, join as join21, resolve as resolve6 } from "node:path";
33411
33493
  import { promisify as promisify4 } from "node:util";
33412
33494
 
33413
33495
  // src/whole-file.ts
33414
- import { renameSync as renameSync3, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
33496
+ import { renameSync as renameSync4, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
33415
33497
  var HELD_FILE_MS = 1000;
33416
33498
  var HELD_FILE_RETRY_MS = 10;
33417
33499
  var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
@@ -33425,7 +33507,7 @@ function writeWhole(path, text) {
33425
33507
  const until = Date.now() + HELD_FILE_MS;
33426
33508
  for (;; ) {
33427
33509
  try {
33428
- renameSync3(partial, path);
33510
+ renameSync4(partial, path);
33429
33511
  return true;
33430
33512
  } catch (error) {
33431
33513
  const code = error.code ?? "";
@@ -33443,20 +33525,20 @@ function writeWhole(path, text) {
33443
33525
  // src/run-record.ts
33444
33526
  var execFileAsync = promisify4(execFile4);
33445
33527
  function runsDirectory() {
33446
- return join20(runtimeDirectory(), "runs");
33528
+ return join21(runtimeDirectory(), "runs");
33447
33529
  }
33448
33530
  function realAsFarAsItExists(path) {
33449
33531
  const missing = [];
33450
33532
  let at = path;
33451
33533
  for (;; ) {
33452
33534
  try {
33453
- return join20(realpathSync6.native(at), ...missing);
33535
+ return join21(realpathSync6.native(at), ...missing);
33454
33536
  } catch {
33455
- const parent = dirname14(at);
33537
+ const parent = dirname15(at);
33456
33538
  if (parent === at) {
33457
33539
  return path;
33458
33540
  }
33459
- missing.unshift(basename4(at));
33541
+ missing.unshift(basename5(at));
33460
33542
  at = parent;
33461
33543
  }
33462
33544
  }
@@ -33467,10 +33549,10 @@ function projectKey(projectPath) {
33467
33549
  return createHash3("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
33468
33550
  }
33469
33551
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
33470
- return join20(directory, "runs", `note-${projectKey(projectPath)}.json`);
33552
+ return join21(directory, "runs", `note-${projectKey(projectPath)}.json`);
33471
33553
  }
33472
33554
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
33473
- return join20(directory, "runs", `latest-${projectKey(servedBy)}.json`);
33555
+ return join21(directory, "runs", `latest-${projectKey(servedBy)}.json`);
33474
33556
  }
33475
33557
  function writeLatestRun(servedBy, latest) {
33476
33558
  try {
@@ -33480,7 +33562,7 @@ function writeLatestRun(servedBy, latest) {
33480
33562
  }
33481
33563
  function readLatestRun(servedBy, directory = runtimeDirectory()) {
33482
33564
  try {
33483
- const fields = JSON.parse(readFileSync18(latestRunPath(servedBy, directory), "utf8"));
33565
+ const fields = JSON.parse(readFileSync19(latestRunPath(servedBy, directory), "utf8"));
33484
33566
  if (typeof fields !== "object" || fields === null) {
33485
33567
  return null;
33486
33568
  }
@@ -33496,17 +33578,17 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
33496
33578
  var SHARED_NOTE = "run.json";
33497
33579
  function openTranscript(startedAt) {
33498
33580
  mkdirSync12(runsDirectory(), { recursive: true });
33499
- const path = join20(runsDirectory(), `run-${startedAt}.log`);
33581
+ const path = join21(runsDirectory(), `run-${startedAt}.log`);
33500
33582
  return { path, fd: openSync2(path, "a") };
33501
33583
  }
33502
33584
  function profilePath(startedAt) {
33503
- return join20(runsDirectory(), `run-${startedAt}.profile.json`);
33585
+ return join21(runsDirectory(), `run-${startedAt}.profile.json`);
33504
33586
  }
33505
33587
  function noteIsAbout(note, playing) {
33506
33588
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
33507
33589
  }
33508
33590
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
33509
- return join20(directory, "runs", `editor-${projectKey(projectPath)}.json`);
33591
+ return join21(directory, "runs", `editor-${projectKey(projectPath)}.json`);
33510
33592
  }
33511
33593
  function writeEditorRunNote(note) {
33512
33594
  mkdirSync12(runsDirectory(), { recursive: true });
@@ -33517,7 +33599,7 @@ function writeEditorRunNote(note) {
33517
33599
  }
33518
33600
  function readEditorRunNote(projectPath) {
33519
33601
  for (const directory of runtimeDirectories()) {
33520
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join20(directory, "runs", "editor-run.json"));
33602
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join21(directory, "runs", "editor-run.json"));
33521
33603
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
33522
33604
  return note;
33523
33605
  }
@@ -33527,7 +33609,7 @@ function readEditorRunNote(projectPath) {
33527
33609
  function editorNoteAt(path) {
33528
33610
  let parsed;
33529
33611
  try {
33530
- parsed = JSON.parse(readFileSync18(path, "utf8"));
33612
+ parsed = JSON.parse(readFileSync19(path, "utf8"));
33531
33613
  } catch {
33532
33614
  return null;
33533
33615
  }
@@ -33551,7 +33633,7 @@ function readRunRecord(projectPath) {
33551
33633
  if (own !== null) {
33552
33634
  return own;
33553
33635
  }
33554
- const shared = recordAt(join20(directory, "runs", SHARED_NOTE));
33636
+ const shared = recordAt(join21(directory, "runs", SHARED_NOTE));
33555
33637
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
33556
33638
  return shared;
33557
33639
  }
@@ -33563,13 +33645,13 @@ function everyRunRecord() {
33563
33645
  for (const directory of runtimeDirectories()) {
33564
33646
  let entries;
33565
33647
  try {
33566
- entries = readdirSync7(join20(directory, "runs"));
33648
+ entries = readdirSync8(join21(directory, "runs"));
33567
33649
  } catch {
33568
33650
  continue;
33569
33651
  }
33570
33652
  for (const entry of entries) {
33571
33653
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
33572
- const record = recordAt(join20(directory, "runs", entry));
33654
+ const record = recordAt(join21(directory, "runs", entry));
33573
33655
  if (record !== null) {
33574
33656
  found.push(record);
33575
33657
  }
@@ -33581,7 +33663,7 @@ function everyRunRecord() {
33581
33663
  function recordAt(path) {
33582
33664
  let parsed;
33583
33665
  try {
33584
- parsed = JSON.parse(readFileSync18(path, "utf8"));
33666
+ parsed = JSON.parse(readFileSync19(path, "utf8"));
33585
33667
  } catch {
33586
33668
  return null;
33587
33669
  }
@@ -33634,7 +33716,7 @@ async function runningAs(pid) {
33634
33716
  const began = await startedAt(pid, null);
33635
33717
  const when = began === null ? {} : { startedAt: began };
33636
33718
  if (process.platform === "linux") {
33637
- const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
33719
+ const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
33638
33720
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
33639
33721
  }
33640
33722
  const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
@@ -33741,7 +33823,7 @@ function whyNotTheRun(record, running, needed) {
33741
33823
  if (after > SAME_RUN_WINDOW_MS) {
33742
33824
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
33743
33825
  }
33744
- const engine = record.command === undefined ? null : basename4(record.command);
33826
+ const engine = record.command === undefined ? null : basename5(record.command);
33745
33827
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
33746
33828
  const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
33747
33829
  if (wanted !== null && !said.includes(wanted)) {
@@ -33780,7 +33862,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
33780
33862
  }
33781
33863
  function clearRunRecord(projectPath) {
33782
33864
  for (const directory of runtimeDirectories()) {
33783
- const shared = join20(directory, "runs", SHARED_NOTE);
33865
+ const shared = join21(directory, "runs", SHARED_NOTE);
33784
33866
  const sharedRecord = recordAt(shared);
33785
33867
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
33786
33868
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
@@ -33805,7 +33887,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
33805
33887
  ]));
33806
33888
  let entries;
33807
33889
  try {
33808
- entries = readdirSync7(directory);
33890
+ entries = readdirSync8(directory);
33809
33891
  } catch {
33810
33892
  return;
33811
33893
  }
@@ -33814,7 +33896,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
33814
33896
  if (!transcript && !entry.endsWith(".partial")) {
33815
33897
  continue;
33816
33898
  }
33817
- const path = join20(directory, entry);
33899
+ const path = join21(directory, entry);
33818
33900
  if (kept.has(path) || ageMs(path, now) < keepMs) {
33819
33901
  continue;
33820
33902
  }
@@ -33825,7 +33907,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
33825
33907
  }
33826
33908
 
33827
33909
  // src/script-origins.ts
33828
- import { join as join21 } from "node:path";
33910
+ import { join as join22 } from "node:path";
33829
33911
  var UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
33830
33912
  function unresolvedClassIn(message) {
33831
33913
  return UNRESOLVED.exec(message)?.[1] ?? null;
@@ -33881,7 +33963,7 @@ function checkWithEngine(godotPath, projectPath) {
33881
33963
  const ran = await runEngine(godotPath, [
33882
33964
  "--headless",
33883
33965
  "--log-file",
33884
- join21(logDir, "engine.log"),
33966
+ join22(logDir, "engine.log"),
33885
33967
  "--path",
33886
33968
  projectPath,
33887
33969
  "--check-only",
@@ -33918,10 +34000,10 @@ function withoutListed(entries, errors) {
33918
34000
  }
33919
34001
 
33920
34002
  // src/script-profile.ts
33921
- import { readFileSync as readFileSync19 } from "node:fs";
34003
+ import { readFileSync as readFileSync20 } from "node:fs";
33922
34004
  function readProfileFile(path) {
33923
34005
  try {
33924
- const parsed = JSON.parse(readFileSync19(path, "utf8"));
34006
+ const parsed = JSON.parse(readFileSync20(path, "utf8"));
33925
34007
  return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
33926
34008
  } catch {
33927
34009
  return null;
@@ -35380,12 +35462,12 @@ var TOOL_SPECS = [
35380
35462
  },
35381
35463
  metrics: {
35382
35464
  effect: "read",
35383
- summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
35465
+ summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. physics_steps_per_second is the physics steps taken a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
35384
35466
  requires: []
35385
35467
  },
35386
35468
  accessibility: {
35387
35469
  effect: "read",
35388
- summary: `what the game tells a screen reader, read through UI Automation as a Windows screen reader reads it, under windows: one tree per window, each element with its role as UI Automation names it (text for static text, button, check_box, group for a control holding others), its name, its value where it has one, its bounds in the window's pixels, the actions a screen reader is offered (click, focus, toggle, set_value, select, expand_collapse, scroll_into_view), and focused on the one holding the focus. The name is what is read aloud, so an empty one is read as nothing; static text takes it from DisplayServer.accessibility_update_set_value, not from a name set on it, and the answer says under _note how many text elements on screen have none. With no control focused, the engine reports the first node under the root as focused, which in a run with the runtime is GdharnessRuntime. Needs a windowed run on Windows started with engineArgs ["--accessibility", "always"] or with the project's accessibility/general/accessibility_support at Always Active, since the engine builds the tree only when asked; reading it takes a second or two, starting PowerShell inside the game so it reaches the game's own desktop`,
35470
+ summary: "what the game tells a screen reader, read through UI Automation as a Windows screen reader reads it, under windows: one tree per window, each element with its role (text for static text, button, check_box, group for a control holding others), name, value, description, placeholder, bounds in the window's pixels, the actions a reader is offered (click, focus, toggle, set_value, select, expand_collapse, scroll_into_view), and focused on the one with the focus. node is the path of the node that made the element; an element a node made as a part of itself has part_of instead, and part: announcement marks the text a window speaks announcements through. An empty name is read as nothing: static text takes it from accessibility_update_set_value, and _note counts the text elements on screen with none. tooltip is the node's tooltip_text, which a Windows reader is not told. With no control focused, the engine reports the first node under the root, GdharnessRuntime, as focused. Needs a windowed run on Windows started with engineArgs [\"--accessibility\", \"always\"] or accessibility_support at Always Active, since the engine builds the tree only when asked; reading takes a second or two",
35389
35471
  requires: []
35390
35472
  }
35391
35473
  },
@@ -35876,16 +35958,16 @@ import { spawnSync } from "node:child_process";
35876
35958
  import { createHash as createHash4 } from "node:crypto";
35877
35959
  import {
35878
35960
  cpSync,
35879
- existsSync as existsSync16,
35961
+ existsSync as existsSync17,
35880
35962
  mkdirSync as mkdirSync13,
35881
- readdirSync as readdirSync8,
35882
- readFileSync as readFileSync20,
35963
+ readdirSync as readdirSync9,
35964
+ readFileSync as readFileSync21,
35883
35965
  rmdirSync,
35884
35966
  rmSync as rmSync11,
35885
35967
  statSync as statSync8,
35886
35968
  writeFileSync as writeFileSync14
35887
35969
  } from "node:fs";
35888
- import { dirname as dirname15, join as join22 } from "node:path";
35970
+ import { dirname as dirname16, join as join23 } from "node:path";
35889
35971
  import { fileURLToPath as fileURLToPath3 } from "node:url";
35890
35972
  var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
35891
35973
  var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
@@ -35904,25 +35986,25 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
35904
35986
  function addonsDigest(names, from) {
35905
35987
  const hash = createHash4("sha256");
35906
35988
  const walk = (directory, relative) => {
35907
- for (const name of readdirSync8(directory).sort()) {
35989
+ for (const name of readdirSync9(directory).sort()) {
35908
35990
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
35909
35991
  continue;
35910
35992
  }
35911
- const path = join22(directory, name);
35993
+ const path = join23(directory, name);
35912
35994
  const inside = `${relative}/${name}`;
35913
35995
  if (statSync8(path).isDirectory()) {
35914
35996
  walk(path, inside);
35915
35997
  } else {
35916
35998
  hash.update(`${inside}
35917
35999
  `);
35918
- hash.update(readFileSync20(path));
36000
+ hash.update(readFileSync21(path));
35919
36001
  hash.update(`
35920
36002
  `);
35921
36003
  }
35922
36004
  }
35923
36005
  };
35924
36006
  for (const name of names) {
35925
- walk(join22(from, name), name);
36007
+ walk(join23(from, name), name);
35926
36008
  }
35927
36009
  return hash.digest("hex");
35928
36010
  }
@@ -35955,22 +36037,22 @@ function installedRuntimeDigest(projectPath) {
35955
36037
  return digestMarker(projectPath, RUNTIME_ADDON);
35956
36038
  }
35957
36039
  function digestMarker(projectPath, addon) {
35958
- const marker = join22(projectPath, "addons", addon, DIGEST_MARKER);
35959
- if (!existsSync16(marker)) {
36040
+ const marker = join23(projectPath, "addons", addon, DIGEST_MARKER);
36041
+ if (!existsSync17(marker)) {
35960
36042
  return null;
35961
36043
  }
35962
- const said = readFileSync20(marker, "utf8").trim();
36044
+ const said = readFileSync21(marker, "utf8").trim();
35963
36045
  return said === "" ? null : said;
35964
36046
  }
35965
36047
  function shippedAddonsDirectory() {
35966
- return join22(dirname15(fileURLToPath3(import.meta.url)), "godot", "addons");
36048
+ return join23(dirname16(fileURLToPath3(import.meta.url)), "godot", "addons");
35967
36049
  }
35968
36050
  function installedAddonVersion(projectPath) {
35969
- const marker = join22(projectPath, "addons", ADDONS[0], VERSION_MARKER);
35970
- if (!existsSync16(marker)) {
36051
+ const marker = join23(projectPath, "addons", ADDONS[0], VERSION_MARKER);
36052
+ if (!existsSync17(marker)) {
35971
36053
  return null;
35972
36054
  }
35973
- const said = readFileSync20(marker, "utf8").trim();
36055
+ const said = readFileSync21(marker, "utf8").trim();
35974
36056
  return said === "" ? null : said;
35975
36057
  }
35976
36058
  function loaderAmong(projectPath, named) {
@@ -35981,7 +36063,7 @@ function loaderAmong(projectPath, named) {
35981
36063
  }
35982
36064
  let source = null;
35983
36065
  try {
35984
- source = readFileSync20(join22(projectPath, path), "utf8");
36066
+ source = readFileSync21(join23(projectPath, path), "utf8");
35985
36067
  } catch {}
35986
36068
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
35987
36069
  return { name: entry, path, exists: true };
@@ -36023,10 +36105,10 @@ function enabledPlugins(settings) {
36023
36105
  function inspectProject(projectPath) {
36024
36106
  const problems = [];
36025
36107
  const addons = ADDONS.map((name) => {
36026
- const directory = join22(projectPath, "addons", name);
36027
- const marker = join22(directory, VERSION_MARKER);
36028
- const installed = existsSync16(directory);
36029
- const version = existsSync16(marker) ? readFileSync20(marker, "utf8").trim() : null;
36108
+ const directory = join23(projectPath, "addons", name);
36109
+ const marker = join23(directory, VERSION_MARKER);
36110
+ const installed = existsSync17(directory);
36111
+ const version = existsSync17(marker) ? readFileSync21(marker, "utf8").trim() : null;
36030
36112
  const current = version === SERVER_VERSION;
36031
36113
  if (!installed) {
36032
36114
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -36035,7 +36117,7 @@ function inspectProject(projectPath) {
36035
36117
  }
36036
36118
  return { name, installed, version, current };
36037
36119
  });
36038
- const settings = parseProjectGodot(readFileSync20(join22(projectPath, "project.godot"), "utf8"));
36120
+ const settings = parseProjectGodot(readFileSync21(join23(projectPath, "project.godot"), "utf8"));
36039
36121
  const pluginsEnabled = enabledPlugins(settings);
36040
36122
  for (const name of EDITOR_PLUGINS) {
36041
36123
  if (!pluginsEnabled.includes(name)) {
@@ -36051,7 +36133,7 @@ function inspectProject(projectPath) {
36051
36133
  const tracked = trackedByGit(projectPath, [...named.values()]);
36052
36134
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
36053
36135
  for (const [name, path] of named) {
36054
- if (!existsSync16(join22(projectPath, path))) {
36136
+ if (!existsSync17(join23(projectPath, path))) {
36055
36137
  if (!addonMissing(path)) {
36056
36138
  problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
36057
36139
  }
@@ -36102,7 +36184,7 @@ function argumentShape(schema) {
36102
36184
  var UPDATE_NOTICE_EVERY = 500;
36103
36185
  var FEEDBACK_NOTICE_EVERY = 250;
36104
36186
  var run4 = promisify5(execFile5);
36105
- var __dirname2 = dirname16(fileURLToPath4(import.meta.url));
36187
+ var __dirname2 = dirname17(fileURLToPath4(import.meta.url));
36106
36188
  var PARENT_ASKED_AFTER_MS = 5000;
36107
36189
  function editorRestartTimeoutMs() {
36108
36190
  return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
@@ -36134,22 +36216,22 @@ function writtenFileRefusal(path, rule) {
36134
36216
  if (/^(res|user):\/\//.test(path)) {
36135
36217
  return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
36136
36218
  }
36137
- if (!isAbsolute3(path)) {
36219
+ if (!isAbsolute4(path)) {
36138
36220
  return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
36139
36221
  }
36140
36222
  if (!rule.extensions.includes(extname3(path).toLowerCase())) {
36141
36223
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
36142
36224
  }
36143
- const directory = realPathOr(dirname16(path));
36144
- if (!existsSync17(directory)) {
36145
- return `${named}'s directory ${dirname16(path)} does not exist; ${what} creates no directories.`;
36225
+ const directory = realPathOr(dirname17(path));
36226
+ if (!existsSync18(directory)) {
36227
+ return `${named}'s directory ${dirname17(path)} does not exist; ${what} creates no directories.`;
36146
36228
  }
36147
- const where = join23(directory, basename5(path));
36229
+ const where = join24(directory, basename6(path));
36148
36230
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
36149
36231
  if (inside !== undefined) {
36150
36232
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
36151
36233
  }
36152
- const taken = rule.writes(where).find((file) => existsSync17(file));
36234
+ const taken = rule.writes(where).find((file) => existsSync18(file));
36153
36235
  if (taken !== undefined) {
36154
36236
  return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
36155
36237
  }
@@ -36199,6 +36281,7 @@ function captureDetail(payload) {
36199
36281
  function patienceForFrames(frames, atLeast) {
36200
36282
  return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
36201
36283
  }
36284
+ var ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.";
36202
36285
  function longerWaitNote(tool, op, waitedMs) {
36203
36286
  const spec = TOOL_SPECS.find((one) => one.name === tool);
36204
36287
  if (spec !== undefined && argumentsOf(spec, op).includes("timeoutMs")) {
@@ -36669,6 +36752,7 @@ function notRunNote(notRun, failFast) {
36669
36752
  function exportAnswer(ending, asked, before, after) {
36670
36753
  const { log } = ending;
36671
36754
  const written = after !== null && after !== before;
36755
+ const notCopied = librariesNotCopied(log.all);
36672
36756
  const verdict = {
36673
36757
  exported: ending.exitCode === 0 && ending.failure === null && log.count("error") === 0 && written,
36674
36758
  ...asked,
@@ -36677,6 +36761,10 @@ function exportAnswer(ending, asked, before, after) {
36677
36761
  failure: ending.failure ?? undefined,
36678
36762
  errors: log.count("error"),
36679
36763
  warnings: log.count("warning"),
36764
+ ...notCopied.length === 0 ? {} : {
36765
+ librariesNotLoaded: notCopied,
36766
+ extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
36767
+ },
36680
36768
  ...foldedEntriesAnswer(log)
36681
36769
  };
36682
36770
  if (verdict.exported) {
@@ -36721,7 +36809,7 @@ var EDITOR_FREE_INSTEAD = {
36721
36809
  scene_tree: "runtime_inspect tree reads the nodes of a game that is running, and needs no editor; scene_tree reads a scene file through the editor"
36722
36810
  };
36723
36811
  function editorAddonMissing(projectPath) {
36724
- if (!existsSync17(join23(projectPath, "project.godot"))) {
36812
+ if (!existsSync18(join24(projectPath, "project.godot"))) {
36725
36813
  return null;
36726
36814
  }
36727
36815
  const report = inspectProject(projectPath);
@@ -37003,7 +37091,7 @@ function profileFrameFunctions(projectPath) {
37003
37091
  let projectGodot = null;
37004
37092
  if (projectPath !== null) {
37005
37093
  try {
37006
- projectGodot = readFileSync21(join23(projectPath, "project.godot"), "utf8");
37094
+ projectGodot = readFileSync22(join24(projectPath, "project.godot"), "utf8");
37007
37095
  } catch {}
37008
37096
  }
37009
37097
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -37029,7 +37117,7 @@ function projectSourceOf(projectPath) {
37029
37117
  let source = null;
37030
37118
  if (inside.ok) {
37031
37119
  try {
37032
- source = readFileSync21(inside.absolutePath, "utf8");
37120
+ source = readFileSync22(inside.absolutePath, "utf8");
37033
37121
  } catch {}
37034
37122
  }
37035
37123
  read.set(script, source);
@@ -37181,7 +37269,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
37181
37269
  var ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd";
37182
37270
  function gdUnitFailsOnScriptErrors(projectPath) {
37183
37271
  try {
37184
- return settingKeys(readFileSync21(join23(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
37272
+ return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
37185
37273
  } catch {
37186
37274
  return true;
37187
37275
  }
@@ -37196,13 +37284,13 @@ function uncaughtNote(uncaught) {
37196
37284
  }
37197
37285
  function elsewhereIn(projectPath, asked) {
37198
37286
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
37199
- if (wanted === "" || existsSync17(join23(projectPath, wanted))) {
37287
+ if (wanted === "" || existsSync18(join24(projectPath, wanted))) {
37200
37288
  return "";
37201
37289
  }
37202
37290
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
37203
37291
  let near = [];
37204
37292
  try {
37205
- near = readdirSync9(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
37293
+ near = readdirSync10(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
37206
37294
  } catch {
37207
37295
  return "";
37208
37296
  }
@@ -37293,17 +37381,17 @@ function isDirectory(path) {
37293
37381
  }
37294
37382
  }
37295
37383
  function scriptsUnder(directory, prefix) {
37296
- if (existsSync17(join23(directory, ".gdignore"))) {
37384
+ if (existsSync18(join24(directory, ".gdignore"))) {
37297
37385
  return [];
37298
37386
  }
37299
37387
  const found = [];
37300
- for (const entry of readdirSync9(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
37388
+ for (const entry of readdirSync10(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
37301
37389
  if (entry.name.startsWith(".")) {
37302
37390
  continue;
37303
37391
  }
37304
37392
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
37305
37393
  if (entry.isDirectory()) {
37306
- found.push(...scriptsUnder(join23(directory, entry.name), path));
37394
+ found.push(...scriptsUnder(join24(directory, entry.name), path));
37307
37395
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
37308
37396
  found.push(path);
37309
37397
  }
@@ -37311,7 +37399,7 @@ function scriptsUnder(directory, prefix) {
37311
37399
  return found;
37312
37400
  }
37313
37401
  function hasMainScene(projectFile) {
37314
- const scene = parseProjectGodot(readFileSync21(projectFile, "utf8"))["application"]?.["run/main_scene"];
37402
+ const scene = parseProjectGodot(readFileSync22(projectFile, "utf8"))["application"]?.["run/main_scene"];
37315
37403
  return typeof scene === "string" && scene !== "";
37316
37404
  }
37317
37405
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -37319,13 +37407,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
37319
37407
  return false;
37320
37408
  }
37321
37409
  try {
37322
- return readFileSync21(join23(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
37410
+ return readFileSync22(join24(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
37323
37411
  } catch {
37324
37412
  return false;
37325
37413
  }
37326
37414
  }
37327
37415
  function editorPlaysHeadless(projectFile) {
37328
- const runArgs = parseProjectGodot(readFileSync21(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
37416
+ const runArgs = parseProjectGodot(readFileSync22(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
37329
37417
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
37330
37418
  }
37331
37419
  function camelCased(params) {
@@ -37345,7 +37433,7 @@ var STATIC_LIST_HINT = { ttlMs: 5 * 60000, cacheScope: "private" };
37345
37433
  class GodotServer {
37346
37434
  stdio = null;
37347
37435
  locator = new GodotLocator;
37348
- operationsScript = join23(__dirname2, "godot", "operations", "godot_operations.gd");
37436
+ operationsScript = join24(__dirname2, "godot", "operations", "godot_operations.gd");
37349
37437
  godotBridge;
37350
37438
  tools = buildToolDefinitions();
37351
37439
  activeProcess = null;
@@ -37370,6 +37458,7 @@ class GodotServer {
37370
37458
  launchedDropped = null;
37371
37459
  closedHere = null;
37372
37460
  inFlight = new Map;
37461
+ callsOnGames = new Set;
37373
37462
  callsTaken = 0;
37374
37463
  clientNoted = null;
37375
37464
  parentCommand = null;
@@ -37601,14 +37690,14 @@ class GodotServer {
37601
37690
  }
37602
37691
  noteTheStart() {
37603
37692
  const cwd = process.cwd();
37604
- const mcpJson = join23(cwd, ".mcp.json");
37693
+ const mcpJson = join24(cwd, ".mcp.json");
37605
37694
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
37606
37695
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
37607
37696
  writeLifecycle("started", {
37608
37697
  argv: process.argv.slice(1),
37609
37698
  cwd,
37610
37699
  project: this.ownProject,
37611
- mcpJson: existsSync17(mcpJson) ? mcpJson : null
37700
+ mcpJson: existsSync18(mcpJson) ? mcpJson : null
37612
37701
  });
37613
37702
  if (predecessor !== null) {
37614
37703
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -37749,7 +37838,7 @@ class GodotServer {
37749
37838
  return checked.response;
37750
37839
  }
37751
37840
  const named = args["projectPath"];
37752
- if (typeof named === "string" && existsSync17(join23(named, "project.godot"))) {
37841
+ if (typeof named === "string" && existsSync18(join24(named, "project.godot"))) {
37753
37842
  this.lastProjectPath = named;
37754
37843
  }
37755
37844
  this.updates.refresh();
@@ -38307,8 +38396,8 @@ class GodotServer {
38307
38396
  if (path === undefined) {
38308
38397
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
38309
38398
  }
38310
- const file = join23(path, "project.godot");
38311
- if (!existsSync17(file)) {
38399
+ const file = join24(path, "project.godot");
38400
+ if (!existsSync18(file)) {
38312
38401
  return {
38313
38402
  ok: false,
38314
38403
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -38354,7 +38443,7 @@ class GodotServer {
38354
38443
  }
38355
38444
  runEnvironment(args) {
38356
38445
  const savesIn = readNonEmptyString(args, "savesIn");
38357
- if (savesIn !== undefined && !isAbsolute3(savesIn)) {
38446
+ if (savesIn !== undefined && !isAbsolute4(savesIn)) {
38358
38447
  return {
38359
38448
  ok: false,
38360
38449
  response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
@@ -38938,11 +39027,11 @@ class GodotServer {
38938
39027
  if (!engine.ok) {
38939
39028
  return engine.response;
38940
39029
  }
38941
- const application = parseProjectGodot(readFileSync21(project.value.file, "utf8"))["application"] ?? {};
39030
+ const application = parseProjectGodot(readFileSync22(project.value.file, "utf8"))["application"] ?? {};
38942
39031
  const name = application["config/name"];
38943
39032
  const mainScene = application["run/main_scene"];
38944
39033
  const info = {
38945
- name: typeof name === "string" && name !== "" ? name : basename5(project.value.path),
39034
+ name: typeof name === "string" && name !== "" ? name : basename6(project.value.path),
38946
39035
  path: project.value.path,
38947
39036
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
38948
39037
  godotVersion: await this.godotVersion(engine.value),
@@ -38994,18 +39083,18 @@ class GodotServer {
38994
39083
  if (!engine.ok) {
38995
39084
  return engine.response;
38996
39085
  }
38997
- const folder = dirname16(output.absolutePath);
39086
+ const folder = dirname17(output.absolutePath);
38998
39087
  try {
38999
39088
  mkdirSync14(folder, { recursive: true });
39000
39089
  } catch (error) {
39001
- return this.createErrorResponse(`Cannot create ${dirname16(output.relativePath)} to export into: ${errorMessage(error)}`);
39090
+ return this.createErrorResponse(`Cannot create ${dirname17(output.relativePath)} to export into: ${errorMessage(error)}`);
39002
39091
  }
39003
39092
  const debug = readBoolean(args, "debug") ?? false;
39004
39093
  const exportLogs = scratchDirectory("export");
39005
39094
  const exportArgs = [
39006
39095
  "--headless",
39007
39096
  "--log-file",
39008
- join23(exportLogs, "engine.log"),
39097
+ join24(exportLogs, "engine.log"),
39009
39098
  "--path",
39010
39099
  project.value.path,
39011
39100
  debug ? "--export-debug" : "--export-release",
@@ -39013,8 +39102,12 @@ class GodotServer {
39013
39102
  output.absolutePath
39014
39103
  ];
39015
39104
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
39016
- const writtenAt = () => existsSync17(output.absolutePath) ? statSync9(output.absolutePath).mtimeMs : null;
39105
+ const writtenAt = () => existsSync18(output.absolutePath) ? statSync9(output.absolutePath).mtimeMs : null;
39017
39106
  const before = writtenAt();
39107
+ const freed = freeLibraryCopies(project.value.path);
39108
+ if (freed.setAside.length > 0 || freed.swept.length > 0) {
39109
+ this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
39110
+ }
39018
39111
  let ending;
39019
39112
  try {
39020
39113
  ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
@@ -39077,10 +39170,10 @@ class GodotServer {
39077
39170
  if (movie !== null) {
39078
39171
  return this.createErrorResponse(movie);
39079
39172
  }
39080
- const missing = paths.value.filter((path) => !existsSync17(join23(project.value.path, path.slice("res://".length))));
39173
+ const missing = paths.value.filter((path) => !existsSync18(join24(project.value.path, path.slice("res://".length))));
39081
39174
  const asked = paths.value.filter((path) => !missing.includes(path));
39082
39175
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
39083
- const noRunner = !existsSync17(join23(project.value.path, runner));
39176
+ const noRunner = !existsSync18(join24(project.value.path, runner));
39084
39177
  if (asked.length === 0) {
39085
39178
  const verdict = `nothing at ${missing.join(" and ")}`;
39086
39179
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -39171,21 +39264,21 @@ class GodotServer {
39171
39264
  await untilGone([run.process.pid]);
39172
39265
  }
39173
39266
  await run.settled;
39174
- const reportsDir = join23(project.value.path, ".godot", ...ours.split("/"));
39267
+ const reportsDir = join24(project.value.path, ".godot", ...ours.split("/"));
39175
39268
  let report = null;
39176
39269
  let reportProblem = null;
39177
39270
  try {
39178
- const written = existsSync17(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
39271
+ const written = existsSync18(reportsDir) ? readdirSync10(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
39179
39272
  const newest = written.at(-1);
39180
39273
  if (newest !== undefined) {
39181
- report = parseJUnit(readFileSync21(join23(reportsDir, newest, "results.xml"), "utf8"));
39274
+ report = parseJUnit(readFileSync22(join24(reportsDir, newest, "results.xml"), "utf8"));
39182
39275
  }
39183
39276
  } catch (error) {
39184
39277
  reportProblem = errorMessage(error);
39185
39278
  } finally {
39186
39279
  discard(reportsDir, userData);
39187
39280
  try {
39188
- rmdirSync2(dirname16(reportsDir));
39281
+ rmdirSync2(dirname17(reportsDir));
39189
39282
  } catch {}
39190
39283
  }
39191
39284
  const engineEntries = foldedRepeats(run.log.select({
@@ -39445,11 +39538,11 @@ class GodotServer {
39445
39538
  const cached = cachedClasses(projectPath);
39446
39539
  const sourceOf = (resourcePath) => {
39447
39540
  const contained = resolveWithinProject(projectPath, resourcePath);
39448
- if (!contained.ok || !existsSync17(contained.absolutePath)) {
39541
+ if (!contained.ok || !existsSync18(contained.absolutePath)) {
39449
39542
  return null;
39450
39543
  }
39451
39544
  try {
39452
- return readFileSync21(contained.absolutePath, "utf8");
39545
+ return readFileSync22(contained.absolutePath, "utf8");
39453
39546
  } catch {
39454
39547
  return null;
39455
39548
  }
@@ -39514,11 +39607,11 @@ class GodotServer {
39514
39607
  }
39515
39608
  sourceOfClass(projectPath, resourcePath) {
39516
39609
  const contained = resolveWithinProject(projectPath, resourcePath);
39517
- if (!contained.ok || !existsSync17(contained.absolutePath)) {
39610
+ if (!contained.ok || !existsSync18(contained.absolutePath)) {
39518
39611
  return null;
39519
39612
  }
39520
39613
  try {
39521
- return readFileSync21(contained.absolutePath, "utf8");
39614
+ return readFileSync22(contained.absolutePath, "utf8");
39522
39615
  } catch {
39523
39616
  return null;
39524
39617
  }
@@ -39666,7 +39759,7 @@ class GodotServer {
39666
39759
  ...Array.isArray(stack) ? {
39667
39760
  stack: stack.map((frame) => ({
39668
39761
  ...frame,
39669
- ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join23(projectPath, frame["script"].slice("res://".length)) } : {}
39762
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join24(projectPath, frame["script"].slice("res://".length)) } : {}
39670
39763
  }))
39671
39764
  } : {}
39672
39765
  });
@@ -39936,7 +40029,7 @@ class GodotServer {
39936
40029
  return null;
39937
40030
  }
39938
40031
  try {
39939
- return settingKeys(readFileSync21(join23(projectPath, "project.godot"), "utf8"));
40032
+ return settingKeys(readFileSync22(join24(projectPath, "project.godot"), "utf8"));
39940
40033
  } catch {
39941
40034
  return null;
39942
40035
  }
@@ -40201,6 +40294,7 @@ class GodotServer {
40201
40294
  }
40202
40295
  async openAnEditor(engine, projectPath, ports, hidden) {
40203
40296
  clearEditorLog(projectPath);
40297
+ freeLibraryCopies(projectPath);
40204
40298
  const launched = await launchOutsideTheTree({
40205
40299
  command: engine,
40206
40300
  args: editorArguments(projectPath, ports, editorLogPath(projectPath), hidden),
@@ -40359,7 +40453,7 @@ class GodotServer {
40359
40453
  }
40360
40454
  async runtimeUp(projectPath, before, budget, withArgs) {
40361
40455
  const budgetMs = budget.ms;
40362
- if (!existsSync17(join23(projectPath, RUNTIME_AUTOLOAD.path))) {
40456
+ if (!existsSync18(join24(projectPath, RUNTIME_AUTOLOAD.path))) {
40363
40457
  return runtimeVerdict(null, {
40364
40458
  addon: false,
40365
40459
  budgetMs,
@@ -40638,7 +40732,7 @@ class GodotServer {
40638
40732
  return 0;
40639
40733
  }
40640
40734
  try {
40641
- return reportAlreadyInTranscript(readFileSync21(game.transcript), readFileSync21(report));
40735
+ return reportAlreadyInTranscript(readFileSync22(game.transcript), readFileSync22(report));
40642
40736
  } catch (error) {
40643
40737
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
40644
40738
  return 0;
@@ -41318,7 +41412,7 @@ class GodotServer {
41318
41412
  }
41319
41413
  const going = await this.runStillGoing(run);
41320
41414
  const projectPath = run.projectPath;
41321
- const projectGone = projectPath !== null && !existsSync17(join23(projectPath, "project.godot"));
41415
+ const projectGone = projectPath !== null && !existsSync18(join24(projectPath, "project.godot"));
41322
41416
  if (projectGone && callersName !== undefined) {
41323
41417
  return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
41324
41418
  }
@@ -41606,7 +41700,7 @@ class GodotServer {
41606
41700
  notes.push(endedWithoutACode({
41607
41701
  throughEditor: run.throughEditor,
41608
41702
  announced: announced !== undefined || run.announcedPid !== undefined,
41609
- couldAnnounce: run.projectPath !== null && existsSync17(join23(run.projectPath, RUNTIME_AUTOLOAD.path)),
41703
+ couldAnnounce: run.projectPath !== null && existsSync18(join24(run.projectPath, RUNTIME_AUTOLOAD.path)),
41610
41704
  endedHere: typeof run.endedHere === "string",
41611
41705
  pickedUp: run.pickedUp === true
41612
41706
  }));
@@ -42171,7 +42265,7 @@ class GodotServer {
42171
42265
  if (!scriptPath.ok) {
42172
42266
  return this.createErrorResponse(scriptPath.reason);
42173
42267
  }
42174
- if (!existsSync17(scriptPath.absolutePath)) {
42268
+ if (!existsSync18(scriptPath.absolutePath)) {
42175
42269
  return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
42176
42270
  }
42177
42271
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -42499,7 +42593,7 @@ class GodotServer {
42499
42593
  async ownRunOnItsWay(projectPath, budgetMs) {
42500
42594
  const run = await this.currentRun();
42501
42595
  const project = projectPath ?? run?.projectPath ?? null;
42502
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync17(join23(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
42596
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync18(join24(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
42503
42597
  return null;
42504
42598
  }
42505
42599
  const already = runtimesAnnounced().running;
@@ -42584,12 +42678,25 @@ class GodotServer {
42584
42678
  }
42585
42679
  }
42586
42680
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
42587
- const screenshotPath = screenshotDir ? join23(screenshotDir, "capture.png") : null;
42681
+ const screenshotPath = screenshotDir ? join24(screenshotDir, "capture.png") : null;
42588
42682
  const staleNote = this.runtimeStaleNote(choice.endpoint);
42589
42683
  const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
42590
42684
  const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
42591
42685
 
42592
42686
  ${staleNote}`);
42687
+ const onGame = {
42688
+ pid: choice.endpoint.pid,
42689
+ what: `${asked.tool} ${asked.op}`,
42690
+ alongside: new Set
42691
+ };
42692
+ for (const other of this.callsOnGames) {
42693
+ if (other.pid === onGame.pid) {
42694
+ other.alongside.add(onGame.what);
42695
+ onGame.alongside.add(other.what);
42696
+ }
42697
+ }
42698
+ this.callsOnGames.add(onGame);
42699
+ const alongside = () => onGame.alongside.size === 0 ? {} : { ranAlongside: [...onGame.alongside], alongsideNote: ALONGSIDE_NOTE };
42593
42700
  try {
42594
42701
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
42595
42702
  if (!reply.ok && reply.requestId !== undefined) {
@@ -42600,7 +42707,8 @@ ${staleNote}`);
42600
42707
  waitedMs: timeoutMs,
42601
42708
  note: `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}`,
42602
42709
  ...answeredBy,
42603
- ...stale
42710
+ ...stale,
42711
+ ...alongside()
42604
42712
  });
42605
42713
  }
42606
42714
  if (!reply.ok) {
@@ -42608,7 +42716,7 @@ ${staleNote}`);
42608
42716
  }
42609
42717
  const { id: _id, ...payload } = reply.payload;
42610
42718
  if (!expectsScreenshot) {
42611
- return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale });
42719
+ return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale, ...alongside() });
42612
42720
  }
42613
42721
  const returnedPath = readString(payload, "path");
42614
42722
  if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
@@ -42625,18 +42733,22 @@ ${staleNote}`);
42625
42733
  return this.createErrorResponse(`The capture was taken and could not be saved at ${saveTo}: ${errorMessage(error)}`);
42626
42734
  }
42627
42735
  }
42736
+ const ranWith = [...onGame.alongside];
42628
42737
  return {
42629
42738
  content: [
42630
42739
  {
42631
42740
  type: "text",
42632
- text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${staleNote === undefined ? "" : `
42741
+ text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${ranWith.length === 0 ? "" : `
42742
+
42743
+ Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}${staleNote === undefined ? "" : `
42633
42744
 
42634
42745
  ${staleNote}`}`
42635
42746
  },
42636
- { type: "image", data: readFileSync21(screenshotPath).toString("base64"), mimeType: "image/png" }
42747
+ { type: "image", data: readFileSync22(screenshotPath).toString("base64"), mimeType: "image/png" }
42637
42748
  ]
42638
42749
  };
42639
42750
  } finally {
42751
+ this.callsOnGames.delete(onGame);
42640
42752
  discard(screenshotDir);
42641
42753
  }
42642
42754
  }