gdharness 1.1.46 → 1.1.48

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
@@ -10249,17 +10249,17 @@ import {
10249
10249
  closeSync as closeSync2,
10250
10250
  constants,
10251
10251
  copyFileSync,
10252
- existsSync as existsSync16,
10252
+ existsSync as existsSync17,
10253
10253
  fstatSync,
10254
- mkdirSync as mkdirSync12,
10254
+ mkdirSync as mkdirSync13,
10255
10255
  openSync as openSync3,
10256
10256
  readdirSync as readdirSync9,
10257
- readFileSync as readFileSync19,
10257
+ readFileSync as readFileSync20,
10258
10258
  readSync as readSync2,
10259
10259
  rmdirSync as rmdirSync2,
10260
10260
  statSync as statSync8
10261
10261
  } from "node:fs";
10262
- import { basename as basename5, dirname as dirname14, extname as extname2, isAbsolute as isAbsolute3, join as join20, normalize as normalize2, relative as relative4 } from "node:path";
10262
+ import { basename as basename5, dirname as dirname15, extname as extname2, isAbsolute as isAbsolute3, join as join22, normalize as normalize2, relative as relative4 } from "node:path";
10263
10263
  import { setTimeout as delay3 } from "node:timers/promises";
10264
10264
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10265
10265
  import { promisify as promisify5 } from "node:util";
@@ -26178,11 +26178,14 @@ function withdrawBridge(path) {
26178
26178
  // src/call-signal.ts
26179
26179
  import { AsyncLocalStorage } from "node:async_hooks";
26180
26180
  var current = new AsyncLocalStorage;
26181
- function withCallSignal(signal, answer) {
26182
- return signal === undefined ? answer() : current.run(signal, answer);
26181
+ function withCallSignal(signal, answer, progress) {
26182
+ return signal === undefined && progress === undefined ? answer() : current.run({ signal, progress }, answer);
26183
26183
  }
26184
26184
  function callSignal() {
26185
- return current.getStore();
26185
+ return current.getStore()?.signal;
26186
+ }
26187
+ function callProgress() {
26188
+ return current.getStore()?.progress;
26186
26189
  }
26187
26190
 
26188
26191
  // src/class-cache.ts
@@ -26945,6 +26948,41 @@ function configDisagrees(projectPath, serverVersion) {
26945
26948
  return `.mcp.json in this project names gdharness@${others.join(" and gdharness@")}, and this server ` + `is ${serverVersion}. Whichever is meant to be right, nothing will change version until the ` + "MCP server is reconnected, and a pin kept anywhere else does not reach this file.";
26946
26949
  }
26947
26950
 
26951
+ // src/cut-test-run.ts
26952
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync10, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
26953
+ import { dirname as dirname6, join as join9 } from "node:path";
26954
+ function keptAt(projectPath) {
26955
+ return join9(projectPath, ".godot", "gdharness-reports", "cut-run.json");
26956
+ }
26957
+ function keepCutTestRun(projectPath, run) {
26958
+ const path = keptAt(projectPath);
26959
+ try {
26960
+ mkdirSync6(dirname6(path), { recursive: true });
26961
+ writeFileSync6(path, `${JSON.stringify(run, null, 2)}
26962
+ `, "utf8");
26963
+ } catch {}
26964
+ }
26965
+ function takeCutTestRun(projectPath) {
26966
+ const path = keptAt(projectPath);
26967
+ if (!existsSync5(path)) {
26968
+ return null;
26969
+ }
26970
+ try {
26971
+ const run = JSON.parse(readFileSync10(path, "utf8"));
26972
+ rmSync3(path, { force: true });
26973
+ return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
26974
+ } catch {
26975
+ rmSync3(path, { force: true });
26976
+ return null;
26977
+ }
26978
+ }
26979
+ function cutTestRunNote(run) {
26980
+ const finished = run.suitesFinished?.length ?? 0;
26981
+ const failing = run.suitesFailing?.length ?? 0;
26982
+ const got = finished === 0 ? "before any suite had finished" : `after ${finished === 1 ? "one suite" : `${finished} suites`} had finished${failing === 0 ? "" : finished === 1 ? ", with failures" : `, ${failing} of them with failures`}`;
26983
+ return `The test run before this one in this project, of ${run.paths.join(", ")}, was cut off by its client at ${run.cutAt} ${got}${run.suiteRunning === undefined ? "" : `, while ${run.suiteRunning} was running`}; what it finished is under previousRunCut.`;
26984
+ }
26985
+
26948
26986
  // src/dap_client.ts
26949
26987
  import { createConnection as createConnection2 } from "node:net";
26950
26988
  import { setTimeout as delay2 } from "node:timers/promises";
@@ -27737,16 +27775,16 @@ function dictionary(entries) {
27737
27775
  // src/editor-log.ts
27738
27776
  import {
27739
27777
  closeSync,
27740
- existsSync as existsSync5,
27741
- mkdirSync as mkdirSync6,
27778
+ existsSync as existsSync6,
27779
+ mkdirSync as mkdirSync7,
27742
27780
  openSync,
27743
- readFileSync as readFileSync10,
27781
+ readFileSync as readFileSync11,
27744
27782
  readSync,
27745
- rmSync as rmSync3,
27783
+ rmSync as rmSync4,
27746
27784
  statSync as statSync3,
27747
- writeFileSync as writeFileSync6
27785
+ writeFileSync as writeFileSync7
27748
27786
  } from "node:fs";
27749
- import { dirname as dirname6, join as join9 } from "node:path";
27787
+ import { dirname as dirname7, join as join10 } from "node:path";
27750
27788
 
27751
27789
  // src/game-log.ts
27752
27790
  import { StringDecoder } from "node:string_decoder";
@@ -27864,14 +27902,14 @@ function answersTo(entry, severity, contains) {
27864
27902
 
27865
27903
  // src/editor-log.ts
27866
27904
  function editorLogPath(projectPath) {
27867
- return join9(projectPath, ".godot", "gdharness-editor.log");
27905
+ return join10(projectPath, ".godot", "gdharness-editor.log");
27868
27906
  }
27869
27907
  function editorLogNotePath(projectPath) {
27870
- return join9(projectPath, ".godot", "gdharness-editor.json");
27908
+ return join10(projectPath, ".godot", "gdharness-editor.json");
27871
27909
  }
27872
27910
  function readEditorLogNote(projectPath) {
27873
27911
  try {
27874
- const fields = asParams(JSON.parse(readFileSync10(editorLogNotePath(projectPath), "utf8")));
27912
+ const fields = asParams(JSON.parse(readFileSync11(editorLogNotePath(projectPath), "utf8")));
27875
27913
  const pid = readNumber(fields, "pid");
27876
27914
  const startedAt = readString(fields, "startedAt");
27877
27915
  return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
@@ -27882,16 +27920,16 @@ function readEditorLogNote(projectPath) {
27882
27920
  function clearEditorLog(projectPath) {
27883
27921
  try {
27884
27922
  const path = editorLogPath(projectPath);
27885
- mkdirSync6(dirname6(path), { recursive: true });
27886
- rmSync3(path, { force: true });
27887
- rmSync3(editorLogNotePath(projectPath), { force: true });
27923
+ mkdirSync7(dirname7(path), { recursive: true });
27924
+ rmSync4(path, { force: true });
27925
+ rmSync4(editorLogNotePath(projectPath), { force: true });
27888
27926
  } catch {}
27889
27927
  }
27890
27928
  function writeEditorLogNote(projectPath, pid) {
27891
27929
  try {
27892
27930
  const path = editorLogNotePath(projectPath);
27893
- mkdirSync6(dirname6(path), { recursive: true });
27894
- writeFileSync6(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
27931
+ mkdirSync7(dirname7(path), { recursive: true });
27932
+ writeFileSync7(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
27895
27933
  } catch {}
27896
27934
  }
27897
27935
  function theConsoleWasNotCaptured(why, projectPath) {
@@ -27912,7 +27950,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
27912
27950
  return { kind: "older server", notedPid: noted?.pid ?? null };
27913
27951
  }
27914
27952
  const path = editorLogPath(projectPath);
27915
- if (!existsSync5(path)) {
27953
+ if (!existsSync6(path)) {
27916
27954
  return { kind: "not written" };
27917
27955
  }
27918
27956
  const log = new GameLog;
@@ -27925,7 +27963,7 @@ var MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
27925
27963
  function theFirstOf(path) {
27926
27964
  const whole = statSync3(path).size;
27927
27965
  if (whole <= MOST_CONSOLE_BYTES) {
27928
- return { text: readFileSync10(path, "utf8") };
27966
+ return { text: readFileSync11(path, "utf8") };
27929
27967
  }
27930
27968
  const handle = openSync(path, "r");
27931
27969
  try {
@@ -28693,14 +28731,14 @@ function getDefaultBridge(ownProject = null) {
28693
28731
 
28694
28732
  // src/godot-path.ts
28695
28733
  import { execFile as execFile2 } from "node:child_process";
28696
- import { existsSync as existsSync7 } from "node:fs";
28734
+ import { existsSync as existsSync8 } from "node:fs";
28697
28735
  import { normalize } from "node:path";
28698
28736
  import { promisify as promisify2 } from "node:util";
28699
28737
 
28700
28738
  // src/detection.ts
28701
- import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
28739
+ import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
28702
28740
  import { homedir as homedir2 } from "node:os";
28703
- import { join as join10 } from "node:path";
28741
+ import { join as join11 } from "node:path";
28704
28742
  function resolveHomeDirectory() {
28705
28743
  try {
28706
28744
  return homedir2();
@@ -28709,7 +28747,7 @@ function resolveHomeDirectory() {
28709
28747
  }
28710
28748
  }
28711
28749
  function scanDirectoryForGodotBinaries(directory, platform) {
28712
- if (!directory || !existsSync6(directory)) {
28750
+ if (!directory || !existsSync7(directory)) {
28713
28751
  return [];
28714
28752
  }
28715
28753
  let entries;
@@ -28724,7 +28762,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
28724
28762
  if (!pattern.test(name)) {
28725
28763
  continue;
28726
28764
  }
28727
- const fullPath = join10(directory, name);
28765
+ const fullPath = join11(directory, name);
28728
28766
  try {
28729
28767
  const stat = statSync4(fullPath);
28730
28768
  if (stat.isFile()) {
@@ -28733,7 +28771,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
28733
28771
  } catch {}
28734
28772
  }
28735
28773
  matches.sort((a, b) => b.mtime - a.mtime);
28736
- return matches.map((m) => join10(directory, m.name));
28774
+ return matches.map((m) => join11(directory, m.name));
28737
28775
  }
28738
28776
  function conventionalPaths(platform, home) {
28739
28777
  const paths = ["godot"];
@@ -28836,7 +28874,7 @@ class GodotLocator {
28836
28874
  return known;
28837
28875
  }
28838
28876
  let reason;
28839
- if (path !== "godot" && !existsSync7(path)) {
28877
+ if (path !== "godot" && !existsSync8(path)) {
28840
28878
  reason = "does not exist";
28841
28879
  } else {
28842
28880
  try {
@@ -28852,33 +28890,33 @@ class GodotLocator {
28852
28890
  }
28853
28891
 
28854
28892
  // src/headless.ts
28855
- import { existsSync as existsSync10, readFileSync as readFileSync12, realpathSync as realpathSync2, writeFileSync as writeFileSync8 } from "node:fs";
28856
- import { dirname as dirname7, join as join13 } from "node:path";
28893
+ import { existsSync as existsSync11, readFileSync as readFileSync13, realpathSync as realpathSync2, writeFileSync as writeFileSync9 } from "node:fs";
28894
+ import { dirname as dirname8, join as join14 } from "node:path";
28857
28895
 
28858
28896
  // src/reimport.ts
28859
28897
  import { createHash as createHash2 } from "node:crypto";
28860
- import { existsSync as existsSync8, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync5, utimesSync, writeFileSync as writeFileSync7 } from "node:fs";
28861
- import { basename as basename2, join as join11 } from "node:path";
28898
+ import { existsSync as existsSync9, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync5, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
28899
+ import { basename as basename2, join as join12 } from "node:path";
28862
28900
  function forceNextImport(projectPath, resourcePath) {
28863
28901
  const relative = resourcePath.replace(/^res:\/\//, "");
28864
- const sidecar = join11(projectPath, `${relative}.import`);
28865
- if (!existsSync8(sidecar)) {
28902
+ const sidecar = join12(projectPath, `${relative}.import`);
28903
+ if (!existsSync9(sidecar)) {
28866
28904
  return;
28867
28905
  }
28868
28906
  const { atime, mtimeMs } = statSync5(sidecar);
28869
- writeFileSync7(sidecar, readFileSync11(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
28907
+ writeFileSync8(sidecar, readFileSync12(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
28870
28908
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
28871
- rmSync4(importRecord(projectPath, resourcePath), { force: true });
28909
+ rmSync5(importRecord(projectPath, resourcePath), { force: true });
28872
28910
  }
28873
28911
  function importRecord(projectPath, resourcePath) {
28874
28912
  const digest = createHash2("md5").update(resourcePath).digest("hex");
28875
- return join11(projectPath, ".godot", "imported", `${basename2(resourcePath)}-${digest}.md5`);
28913
+ return join12(projectPath, ".godot", "imported", `${basename2(resourcePath)}-${digest}.md5`);
28876
28914
  }
28877
28915
 
28878
28916
  // src/scratch.ts
28879
- import { existsSync as existsSync9, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
28917
+ import { existsSync as existsSync10, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync6 } from "node:fs";
28880
28918
  import { tmpdir as tmpdir3 } from "node:os";
28881
- import { join as join12 } from "node:path";
28919
+ import { join as join13 } from "node:path";
28882
28920
  import process7 from "node:process";
28883
28921
  var SWEPT = { recursive: true, force: true };
28884
28922
  var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
@@ -28893,7 +28931,7 @@ var KINDS = [
28893
28931
  "tests"
28894
28932
  ];
28895
28933
  function scratchDirectory(kind) {
28896
- return mkdtempSync(join12(tmpdir3(), `gdharness-${kind}-${process7.pid}-`));
28934
+ return mkdtempSync(join13(tmpdir3(), `gdharness-${kind}-${process7.pid}-`));
28897
28935
  }
28898
28936
  function sweepAbandonedScratch(directory = tmpdir3()) {
28899
28937
  let entries;
@@ -28909,7 +28947,7 @@ function sweepAbandonedScratch(directory = tmpdir3()) {
28909
28947
  continue;
28910
28948
  }
28911
28949
  try {
28912
- remove(join12(directory, entry));
28950
+ remove(join13(directory, entry));
28913
28951
  } catch {}
28914
28952
  }
28915
28953
  }
@@ -28935,7 +28973,7 @@ function discard(...paths) {
28935
28973
  return left;
28936
28974
  }
28937
28975
  function remove(path) {
28938
- rmSync5(path, SWEPT);
28976
+ rmSync6(path, SWEPT);
28939
28977
  }
28940
28978
  function tryAgain(path, attempt) {
28941
28979
  if (attempt === 0) {
@@ -28947,7 +28985,7 @@ function tryAgain(path, attempt) {
28947
28985
  lastTryOnExit();
28948
28986
  }
28949
28987
  const after = AGAIN_AFTER_MS[attempt];
28950
- if (after === undefined || !existsSync9(path)) {
28988
+ if (after === undefined || !existsSync10(path)) {
28951
28989
  waiting.delete(path);
28952
28990
  if (after === undefined) {
28953
28991
  atExit.add(path);
@@ -29027,7 +29065,7 @@ function reason(log, problems) {
29027
29065
  }
29028
29066
  function scriptFault(problems, script) {
29029
29067
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
29030
- const directories = [...new Set([spelled(dirname7(script)), spelled(resolvedOr(dirname7(script)))])];
29068
+ const directories = [...new Set([spelled(dirname8(script)), spelled(resolvedOr(dirname8(script)))])];
29031
29069
  return problems.find((entry) => {
29032
29070
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
29033
29071
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
@@ -29083,7 +29121,7 @@ async function importOnce(godotPath, projectPath) {
29083
29121
  ran = await runEngine(godotPath, [
29084
29122
  "--headless",
29085
29123
  "--log-file",
29086
- join13(logDir, "engine.log"),
29124
+ join14(logDir, "engine.log"),
29087
29125
  "--path",
29088
29126
  projectPath,
29089
29127
  "--import"
@@ -29103,13 +29141,13 @@ async function importOnce(godotPath, projectPath) {
29103
29141
  }
29104
29142
  async function runOperation(engine, operation, params, projectPath) {
29105
29143
  const paramsDir = scratchDirectory("params");
29106
- const paramsFile = join13(paramsDir, `${operation}.json`);
29107
- const answerFile = join13(paramsDir, "answer.json");
29108
- writeFileSync8(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
29144
+ const paramsFile = join14(paramsDir, `${operation}.json`);
29145
+ const answerFile = join14(paramsDir, "answer.json");
29146
+ writeFileSync9(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
29109
29147
  const args = [
29110
29148
  "--headless",
29111
29149
  "--log-file",
29112
- join13(paramsDir, "engine.log"),
29150
+ join14(paramsDir, "engine.log"),
29113
29151
  "--path",
29114
29152
  projectPath,
29115
29153
  "--script",
@@ -29160,10 +29198,10 @@ async function runOperation(engine, operation, params, projectPath) {
29160
29198
  };
29161
29199
  }
29162
29200
  function answerIn(file) {
29163
- if (!existsSync10(file)) {
29201
+ if (!existsSync11(file)) {
29164
29202
  return { kind: "none" };
29165
29203
  }
29166
- const text = readFileSync12(file, "utf8");
29204
+ const text = readFileSync13(file, "utf8");
29167
29205
  try {
29168
29206
  const parsed = JSON.parse(text);
29169
29207
  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)}` };
@@ -29420,12 +29458,16 @@ function whyNoReport(printed, asked) {
29420
29458
  }
29421
29459
  var DISCOVERY_FAILED = "Script errors were detected during test discovery!";
29422
29460
  function scriptErrorsPrinted(printed) {
29461
+ return discoveryListing(printed)?.errors ?? [];
29462
+ }
29463
+ function discoveryListing(printed) {
29423
29464
  const start = printed.findIndex((line) => line.includes(DISCOVERY_FAILED));
29424
29465
  if (start < 0) {
29425
- return [];
29466
+ return null;
29426
29467
  }
29427
29468
  const found = [];
29428
29469
  let message = null;
29470
+ let to = start + 1;
29429
29471
  for (const line of printed.slice(start + 1)) {
29430
29472
  const at = /^\s*at (res:\/\/.+):(\d+)\s*$/.exec(line);
29431
29473
  if (at !== null && message !== null) {
@@ -29436,8 +29478,10 @@ function scriptErrorsPrinted(printed) {
29436
29478
  } else {
29437
29479
  break;
29438
29480
  }
29481
+ to += 1;
29439
29482
  }
29440
- return found.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : (a.line ?? 0) - (b.line ?? 0));
29483
+ const errors = found.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : (a.line ?? 0) - (b.line ?? 0));
29484
+ return { errors, from: start, to };
29441
29485
  }
29442
29486
  var ESCAPE = String.fromCharCode(27);
29443
29487
  var LOCATION = `'[^'\\n]*' in \\S+:\\d+`;
@@ -29573,6 +29617,52 @@ function orphansPrinted(printed) {
29573
29617
  suites
29574
29618
  };
29575
29619
  }
29620
+ var SUITE_COUNTS = /(\d+)\s+test cases\s*\|\s*(\d+)\s+errors\s*\|\s*(\d+)\s+failures/;
29621
+ var CASE_FAILED = /^\s*res:\/\/.+?\.(?:gd|cs) > (.+?) (?:FAILED|ERROR)(?:\s|$)/;
29622
+ function finishesASuite(line) {
29623
+ return SUITE_COUNTS.test(line) && !line.includes(OVERALL);
29624
+ }
29625
+ function suitesPrinted(printed) {
29626
+ const finished = [];
29627
+ let running = null;
29628
+ let failed = [];
29629
+ for (const line of printed) {
29630
+ const started = RUNNING_SUITE.exec(line);
29631
+ if (started?.[1] !== undefined) {
29632
+ running = started[1];
29633
+ failed = [];
29634
+ continue;
29635
+ }
29636
+ if (running === null) {
29637
+ continue;
29638
+ }
29639
+ const failing = CASE_FAILED.exec(line)?.[1];
29640
+ if (failing !== undefined) {
29641
+ failed.push(failing);
29642
+ continue;
29643
+ }
29644
+ const counted = finishesASuite(line) ? SUITE_COUNTS.exec(line) : null;
29645
+ if (counted !== null) {
29646
+ finished.push({
29647
+ path: running,
29648
+ cases: Number(counted[1]),
29649
+ errors: Number(counted[2]),
29650
+ failures: Number(counted[3]),
29651
+ failed
29652
+ });
29653
+ running = null;
29654
+ }
29655
+ }
29656
+ return { finished, running };
29657
+ }
29658
+ function testProgressMessage(printed, elapsedMs) {
29659
+ const { finished, running } = suitesPrinted(printed);
29660
+ const failing = finished.filter((suite) => suite.errors + suite.failures > 0).length;
29661
+ const seconds = Math.floor(elapsedMs / 1000);
29662
+ const took = seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
29663
+ const done = finished.length === 0 ? "No suite has finished" : `${finished.length === 1 ? "One suite has" : `${finished.length} suites have`} finished${failing === 0 ? "" : `, ${failing} with failures`}`;
29664
+ return `${done}${running === null ? "" : `; running ${running}`}; ${took} in.`;
29665
+ }
29576
29666
 
29577
29667
  // src/keeper-channel.ts
29578
29668
  import { connect, createServer as createServer2 } from "node:net";
@@ -29616,10 +29706,10 @@ function askTheKeeperToStop(address, withinMs) {
29616
29706
  }
29617
29707
 
29618
29708
  // src/lsp_client.ts
29619
- import { existsSync as existsSync11, realpathSync as realpathSync3 } from "node:fs";
29709
+ import { existsSync as existsSync12, realpathSync as realpathSync3 } from "node:fs";
29620
29710
  import { readFile, realpath } from "node:fs/promises";
29621
29711
  import { createConnection as createConnection3 } from "node:net";
29622
- import { dirname as dirname8, join as join14, resolve as resolve3 } from "node:path";
29712
+ import { dirname as dirname9, join as join15, resolve as resolve3 } from "node:path";
29623
29713
  import { fileURLToPath, pathToFileURL } from "node:url";
29624
29714
  var DEFAULT_LSP_PORT = 6005;
29625
29715
  var DIAGNOSTICS_TIMEOUT_MS = 5000;
@@ -29776,7 +29866,7 @@ class GodotLSPClient {
29776
29866
  if (this.initialized) {
29777
29867
  return;
29778
29868
  }
29779
- const rootPath = this.rootPath ?? dirname8(resolve3(filePath));
29869
+ const rootPath = this.rootPath ?? dirname9(resolve3(filePath));
29780
29870
  await this.initialize(rootPath);
29781
29871
  }
29782
29872
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -30187,7 +30277,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
30187
30277
  } catch {
30188
30278
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
30189
30279
  }
30190
- if (!existsSync11(join14(projectPath, "project.godot"))) {
30280
+ if (!existsSync12(join15(projectPath, "project.godot"))) {
30191
30281
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
30192
30282
  }
30193
30283
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -30364,14 +30454,14 @@ function secondsFromClock(printed) {
30364
30454
  }
30365
30455
 
30366
30456
  // src/project-scan.ts
30367
- import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync13 } from "node:fs";
30368
- import { join as join15 } from "node:path";
30457
+ import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "node:fs";
30458
+ import { join as join16 } from "node:path";
30369
30459
  var SKIPPED = new Set(["node_modules"]);
30370
30460
  function skipped(name) {
30371
30461
  return name.startsWith(".") || SKIPPED.has(name);
30372
30462
  }
30373
30463
  function steppedOver(directory) {
30374
- return existsSync12(join15(directory, ".gdignore"));
30464
+ return existsSync13(join16(directory, ".gdignore"));
30375
30465
  }
30376
30466
  var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
30377
30467
  var ASSET_EXTENSIONS = new Set([
@@ -30415,7 +30505,7 @@ function projectStructure(projectPath) {
30415
30505
  continue;
30416
30506
  }
30417
30507
  if (entry.isDirectory()) {
30418
- visit(join15(directory, entry.name));
30508
+ visit(join16(directory, entry.name));
30419
30509
  } else if (entry.isFile()) {
30420
30510
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
30421
30511
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -30446,14 +30536,14 @@ function scriptsWithoutUid(projectPath) {
30446
30536
  }
30447
30537
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
30448
30538
  if (entry.isDirectory()) {
30449
- visit(join15(directory, entry.name), spelled);
30539
+ visit(join16(directory, entry.name), spelled);
30450
30540
  continue;
30451
30541
  }
30452
30542
  if (!entry.isFile()) {
30453
30543
  continue;
30454
30544
  }
30455
30545
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
30456
- if (CARRIES_A_UID.has(extension) && !existsSync12(join15(directory, `${entry.name}.uid`))) {
30546
+ if (CARRIES_A_UID.has(extension) && !existsSync13(join16(directory, `${entry.name}.uid`))) {
30457
30547
  found.push(spelled);
30458
30548
  }
30459
30549
  }
@@ -30485,7 +30575,7 @@ function projectTexts(projectPath) {
30485
30575
  if (skipped(entry.name)) {
30486
30576
  continue;
30487
30577
  }
30488
- const absolute = join15(directory, entry.name);
30578
+ const absolute = join16(directory, entry.name);
30489
30579
  const spelled = `${prefix}${entry.name}`;
30490
30580
  if (entry.isDirectory()) {
30491
30581
  visit(absolute, `${spelled}/`);
@@ -30495,7 +30585,7 @@ function projectTexts(projectPath) {
30495
30585
  if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
30496
30586
  continue;
30497
30587
  }
30498
- const bytes = readFileSync13(absolute);
30588
+ const bytes = readFileSync14(absolute);
30499
30589
  if (!isBinary(bytes)) {
30500
30590
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
30501
30591
  }
@@ -30512,7 +30602,7 @@ function ignoredTexts(projectPath) {
30512
30602
  if (skipped(entry.name)) {
30513
30603
  continue;
30514
30604
  }
30515
- const absolute = join15(directory, entry.name);
30605
+ const absolute = join16(directory, entry.name);
30516
30606
  const spelled = `${prefix}${entry.name}`;
30517
30607
  if (entry.isDirectory()) {
30518
30608
  visit(absolute, `${spelled}/`, under);
@@ -30522,7 +30612,7 @@ function ignoredTexts(projectPath) {
30522
30612
  if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
30523
30613
  continue;
30524
30614
  }
30525
- const bytes = readFileSync13(absolute);
30615
+ const bytes = readFileSync14(absolute);
30526
30616
  if (!isBinary(bytes)) {
30527
30617
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
30528
30618
  }
@@ -30552,7 +30642,7 @@ function searchProject(projectPath, options) {
30552
30642
  if (skipped(entry.name)) {
30553
30643
  continue;
30554
30644
  }
30555
- const entryPath = join15(directory, entry.name);
30645
+ const entryPath = join16(directory, entry.name);
30556
30646
  if (entry.isDirectory()) {
30557
30647
  visit(entryPath);
30558
30648
  continue;
@@ -30561,7 +30651,7 @@ function searchProject(projectPath, options) {
30561
30651
  if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
30562
30652
  continue;
30563
30653
  }
30564
- const bytes = readFileSync13(entryPath);
30654
+ const bytes = readFileSync14(entryPath);
30565
30655
  if (extensions === null && isBinary(bytes)) {
30566
30656
  continue;
30567
30657
  }
@@ -30591,8 +30681,8 @@ function searchProject(projectPath, options) {
30591
30681
  }
30592
30682
 
30593
30683
  // src/rename.ts
30594
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync14, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
30595
- import { dirname as dirname9, posix } from "node:path";
30684
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync15, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
30685
+ import { dirname as dirname10, posix } from "node:path";
30596
30686
  class Script {
30597
30687
  path;
30598
30688
  text;
@@ -31314,25 +31404,25 @@ function rewritesOf(plan, texts, move) {
31314
31404
  }
31315
31405
  function applyRewrites(rewrites) {
31316
31406
  for (const rewrite of rewrites) {
31317
- const now = readFileSync14(rewrite.absolute, "utf8");
31407
+ const now = readFileSync15(rewrite.absolute, "utf8");
31318
31408
  if (now !== rewrite.before) {
31319
31409
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
31320
31410
  }
31321
- if (rewrite.writeTo !== rewrite.absolute && existsSync13(rewrite.writeTo)) {
31411
+ if (rewrite.writeTo !== rewrite.absolute && existsSync14(rewrite.writeTo)) {
31322
31412
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
31323
31413
  }
31324
31414
  }
31325
31415
  const done = [];
31326
31416
  try {
31327
31417
  for (const rewrite of rewrites) {
31328
- mkdirSync7(dirname9(rewrite.writeTo), { recursive: true });
31418
+ mkdirSync8(dirname10(rewrite.writeTo), { recursive: true });
31329
31419
  const staging = `${rewrite.writeTo}.gdharness-rename`;
31330
- writeFileSync9(staging, rewrite.after, "utf8");
31420
+ writeFileSync10(staging, rewrite.after, "utf8");
31331
31421
  renameSync(staging, rewrite.writeTo);
31332
31422
  let movedUid = false;
31333
31423
  if (rewrite.writeTo !== rewrite.absolute) {
31334
31424
  unlinkSync2(rewrite.absolute);
31335
- if (existsSync13(`${rewrite.absolute}.uid`)) {
31425
+ if (existsSync14(`${rewrite.absolute}.uid`)) {
31336
31426
  renameSync(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
31337
31427
  movedUid = true;
31338
31428
  }
@@ -31342,7 +31432,7 @@ function applyRewrites(rewrites) {
31342
31432
  } catch (error) {
31343
31433
  for (const { rewrite, movedUid } of done.reverse()) {
31344
31434
  try {
31345
- writeFileSync9(rewrite.absolute, rewrite.before, "utf8");
31435
+ writeFileSync10(rewrite.absolute, rewrite.before, "utf8");
31346
31436
  if (rewrite.writeTo !== rewrite.absolute) {
31347
31437
  unlinkSync2(rewrite.writeTo);
31348
31438
  if (movedUid) {
@@ -31356,11 +31446,11 @@ function applyRewrites(rewrites) {
31356
31446
  }
31357
31447
 
31358
31448
  // src/rename-symbol.ts
31359
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync6, realpathSync as realpathSync5, rmSync as rmSync6, writeFileSync as writeFileSync10 } from "node:fs";
31360
- import { basename as basename3, dirname as dirname10, join as join16, relative as relative3, resolve as resolve5 } from "node:path";
31449
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readdirSync as readdirSync6, realpathSync as realpathSync5, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
31450
+ import { basename as basename3, dirname as dirname11, join as join17, relative as relative3, resolve as resolve5 } from "node:path";
31361
31451
 
31362
31452
  // src/resources.ts
31363
- import { readFileSync as readFileSync15, realpathSync as realpathSync4 } from "node:fs";
31453
+ import { readFileSync as readFileSync16, realpathSync as realpathSync4 } from "node:fs";
31364
31454
  import { extname, resolve as resolve4 } from "node:path";
31365
31455
  var STATIC_RESOURCES = [
31366
31456
  {
@@ -31570,7 +31660,7 @@ function readResourceText(uri, getProjectPath) {
31570
31660
  const parsedUri = parseGodotUri(uri);
31571
31661
  if (parsedUri.kind === "project-info") {
31572
31662
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
31573
- const rawProject = readFileSync15(projectFilePath, "utf-8");
31663
+ const rawProject = readFileSync16(projectFilePath, "utf-8");
31574
31664
  const parsedProject = parseProjectGodot(rawProject);
31575
31665
  return {
31576
31666
  mimeType: "application/json",
@@ -31579,7 +31669,7 @@ function readResourceText(uri, getProjectPath) {
31579
31669
  }
31580
31670
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
31581
31671
  ensureAllowedExtension(parsedUri.kind, filePath);
31582
- const text = readFileSync15(filePath, "utf-8");
31672
+ const text = readFileSync16(filePath, "utf-8");
31583
31673
  return {
31584
31674
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
31585
31675
  text
@@ -31637,9 +31727,9 @@ function onDiskSpelling(path) {
31637
31727
  let at = resolve5(path);
31638
31728
  for (;; ) {
31639
31729
  try {
31640
- return join16(realpathSync5.native(at), ...missing.reverse());
31730
+ return join17(realpathSync5.native(at), ...missing.reverse());
31641
31731
  } catch {
31642
- const parent = dirname10(at);
31732
+ const parent = dirname11(at);
31643
31733
  if (parent === at) {
31644
31734
  return resolve5(path);
31645
31735
  }
@@ -31690,7 +31780,7 @@ async function renameSymbol(request, services) {
31690
31780
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
31691
31781
  };
31692
31782
  }
31693
- if (existsSync14(request.newScriptPath)) {
31783
+ if (existsSync15(request.newScriptPath)) {
31694
31784
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
31695
31785
  }
31696
31786
  }
@@ -31866,7 +31956,7 @@ async function renameSymbol(request, services) {
31866
31956
  scripts,
31867
31957
  from: declaringPath,
31868
31958
  to: moveTo,
31869
- exists: (path) => existsSync14(`${projectPath}/${path.slice("res://".length)}`)
31959
+ exists: (path) => existsSync15(`${projectPath}/${path.slice("res://".length)}`)
31870
31960
  }, plan);
31871
31961
  }
31872
31962
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? null : declaringPath);
@@ -32067,15 +32157,15 @@ ${mention.kind}`;
32067
32157
  var LEFT_ALONE_ANSWER_LINES = 300;
32068
32158
  var detailWrites = 0;
32069
32159
  function writeLeftAloneDetail(projectPath, symbol, detail) {
32070
- const directory = join16(projectPath, ".godot", "gdharness-renames");
32071
- mkdirSync8(directory, { recursive: true });
32160
+ const directory = join17(projectPath, ".godot", "gdharness-renames");
32161
+ mkdirSync9(directory, { recursive: true });
32072
32162
  detailWrites += 1;
32073
- const path = join16(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
32074
- writeFileSync10(path, `${JSON.stringify(detail, null, 2)}
32163
+ const path = join17(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
32164
+ writeFileSync11(path, `${JSON.stringify(detail, null, 2)}
32075
32165
  `, "utf8");
32076
32166
  const kept = readdirSync6(directory).filter((name) => name.endsWith(".json")).sort().reverse();
32077
32167
  for (const old of kept.slice(10)) {
32078
- rmSync6(join16(directory, old), { force: true });
32168
+ rmSync7(join17(directory, old), { force: true });
32079
32169
  }
32080
32170
  return path;
32081
32171
  }
@@ -32098,16 +32188,16 @@ function summaryOf(edits, mentions, rewrites, left) {
32098
32188
  }
32099
32189
 
32100
32190
  // src/restart-note.ts
32101
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "node:fs";
32102
- import { dirname as dirname11, join as join17 } from "node:path";
32191
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "node:fs";
32192
+ import { dirname as dirname12, join as join18 } from "node:path";
32103
32193
  function restartNotePath(projectPath) {
32104
- return join17(projectPath, ".godot", "gdharness-restart.json");
32194
+ return join18(projectPath, ".godot", "gdharness-restart.json");
32105
32195
  }
32106
32196
  function noteRestartBegun(note) {
32107
32197
  const path = restartNotePath(note.projectPath);
32108
32198
  try {
32109
- mkdirSync9(dirname11(path), { recursive: true });
32110
- writeFileSync11(path, `${JSON.stringify(note, null, 2)}
32199
+ mkdirSync10(dirname12(path), { recursive: true });
32200
+ writeFileSync12(path, `${JSON.stringify(note, null, 2)}
32111
32201
  `, "utf8");
32112
32202
  } catch (error) {
32113
32203
  console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
@@ -32115,7 +32205,7 @@ function noteRestartBegun(note) {
32115
32205
  }
32116
32206
  function restartOwed(projectPath) {
32117
32207
  try {
32118
- const parsed = JSON.parse(readFileSync16(restartNotePath(projectPath), "utf8"));
32208
+ const parsed = JSON.parse(readFileSync17(restartNotePath(projectPath), "utf8"));
32119
32209
  if (typeof parsed !== "object" || parsed === null) {
32120
32210
  return null;
32121
32211
  }
@@ -32130,7 +32220,7 @@ function restartOwed(projectPath) {
32130
32220
  }
32131
32221
  function restartSettled(projectPath) {
32132
32222
  try {
32133
- rmSync7(restartNotePath(projectPath), { force: true });
32223
+ rmSync8(restartNotePath(projectPath), { force: true });
32134
32224
  } catch (error) {
32135
32225
  console.error(`[SERVER] Could not clear the restart note: ${errorMessage(error)}`);
32136
32226
  }
@@ -32140,29 +32230,29 @@ function restartSettled(projectPath) {
32140
32230
  import { execFile as execFile4 } from "node:child_process";
32141
32231
  import { createHash as createHash3 } from "node:crypto";
32142
32232
  import {
32143
- mkdirSync as mkdirSync10,
32233
+ mkdirSync as mkdirSync11,
32144
32234
  openSync as openSync2,
32145
32235
  readdirSync as readdirSync7,
32146
- readFileSync as readFileSync17,
32236
+ readFileSync as readFileSync18,
32147
32237
  realpathSync as realpathSync6,
32148
- rmSync as rmSync8,
32238
+ rmSync as rmSync9,
32149
32239
  statSync as statSync6,
32150
- writeFileSync as writeFileSync12
32240
+ writeFileSync as writeFileSync13
32151
32241
  } from "node:fs";
32152
- import { basename as basename4, dirname as dirname12, join as join18, resolve as resolve6 } from "node:path";
32242
+ import { basename as basename4, dirname as dirname13, join as join19, resolve as resolve6 } from "node:path";
32153
32243
  import { promisify as promisify4 } from "node:util";
32154
32244
  var execFileAsync = promisify4(execFile4);
32155
32245
  function runsDirectory() {
32156
- return join18(runtimeDirectory(), "runs");
32246
+ return join19(runtimeDirectory(), "runs");
32157
32247
  }
32158
32248
  function realAsFarAsItExists(path) {
32159
32249
  const missing = [];
32160
32250
  let at = path;
32161
32251
  for (;; ) {
32162
32252
  try {
32163
- return join18(realpathSync6.native(at), ...missing);
32253
+ return join19(realpathSync6.native(at), ...missing);
32164
32254
  } catch {
32165
- const parent = dirname12(at);
32255
+ const parent = dirname13(at);
32166
32256
  if (parent === at) {
32167
32257
  return path;
32168
32258
  }
@@ -32177,27 +32267,27 @@ function projectKey(projectPath) {
32177
32267
  return createHash3("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
32178
32268
  }
32179
32269
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
32180
- return join18(directory, "runs", `note-${projectKey(projectPath)}.json`);
32270
+ return join19(directory, "runs", `note-${projectKey(projectPath)}.json`);
32181
32271
  }
32182
32272
  var SHARED_NOTE = "run.json";
32183
32273
  function openTranscript(startedAt) {
32184
- mkdirSync10(runsDirectory(), { recursive: true });
32185
- const path = join18(runsDirectory(), `run-${startedAt}.log`);
32274
+ mkdirSync11(runsDirectory(), { recursive: true });
32275
+ const path = join19(runsDirectory(), `run-${startedAt}.log`);
32186
32276
  return { path, fd: openSync2(path, "a") };
32187
32277
  }
32188
32278
  function noteIsAbout(note, playing) {
32189
32279
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
32190
32280
  }
32191
32281
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
32192
- return join18(directory, "runs", `editor-${projectKey(projectPath)}.json`);
32282
+ return join19(directory, "runs", `editor-${projectKey(projectPath)}.json`);
32193
32283
  }
32194
32284
  function writeEditorRunNote(note) {
32195
- mkdirSync10(runsDirectory(), { recursive: true });
32196
- writeFileSync12(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
32285
+ mkdirSync11(runsDirectory(), { recursive: true });
32286
+ writeFileSync13(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
32197
32287
  }
32198
32288
  function readEditorRunNote(projectPath) {
32199
32289
  for (const directory of runtimeDirectories()) {
32200
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join18(directory, "runs", "editor-run.json"));
32290
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join19(directory, "runs", "editor-run.json"));
32201
32291
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
32202
32292
  return note;
32203
32293
  }
@@ -32207,7 +32297,7 @@ function readEditorRunNote(projectPath) {
32207
32297
  function editorNoteAt(path) {
32208
32298
  let parsed;
32209
32299
  try {
32210
- parsed = JSON.parse(readFileSync17(path, "utf8"));
32300
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
32211
32301
  } catch {
32212
32302
  return null;
32213
32303
  }
@@ -32231,7 +32321,7 @@ function readRunRecord(projectPath) {
32231
32321
  if (own !== null) {
32232
32322
  return own;
32233
32323
  }
32234
- const shared = recordAt(join18(directory, "runs", SHARED_NOTE));
32324
+ const shared = recordAt(join19(directory, "runs", SHARED_NOTE));
32235
32325
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
32236
32326
  return shared;
32237
32327
  }
@@ -32243,13 +32333,13 @@ function everyRunRecord() {
32243
32333
  for (const directory of runtimeDirectories()) {
32244
32334
  let entries;
32245
32335
  try {
32246
- entries = readdirSync7(join18(directory, "runs"));
32336
+ entries = readdirSync7(join19(directory, "runs"));
32247
32337
  } catch {
32248
32338
  continue;
32249
32339
  }
32250
32340
  for (const entry of entries) {
32251
32341
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
32252
- const record = recordAt(join18(directory, "runs", entry));
32342
+ const record = recordAt(join19(directory, "runs", entry));
32253
32343
  if (record !== null) {
32254
32344
  found.push(record);
32255
32345
  }
@@ -32261,7 +32351,7 @@ function everyRunRecord() {
32261
32351
  function recordAt(path) {
32262
32352
  let parsed;
32263
32353
  try {
32264
- parsed = JSON.parse(readFileSync17(path, "utf8"));
32354
+ parsed = JSON.parse(readFileSync18(path, "utf8"));
32265
32355
  } catch {
32266
32356
  return null;
32267
32357
  }
@@ -32312,7 +32402,7 @@ async function runningAs(pid) {
32312
32402
  const began = await startedAt(pid, null);
32313
32403
  const when = began === null ? {} : { startedAt: began };
32314
32404
  if (process.platform === "linux") {
32315
- const raw = readFileSync17(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
32405
+ const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
32316
32406
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
32317
32407
  }
32318
32408
  const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
@@ -32458,12 +32548,12 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
32458
32548
  }
32459
32549
  function clearRunRecord(projectPath) {
32460
32550
  for (const directory of runtimeDirectories()) {
32461
- const shared = join18(directory, "runs", SHARED_NOTE);
32551
+ const shared = join19(directory, "runs", SHARED_NOTE);
32462
32552
  const sharedRecord = recordAt(shared);
32463
32553
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
32464
32554
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
32465
32555
  try {
32466
- rmSync8(path, { force: true });
32556
+ rmSync9(path, { force: true });
32467
32557
  } catch {}
32468
32558
  }
32469
32559
  }
@@ -32488,31 +32578,124 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
32488
32578
  if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
32489
32579
  continue;
32490
32580
  }
32491
- const path = join18(directory, entry);
32581
+ const path = join19(directory, entry);
32492
32582
  if (kept.has(path) || ageMs(path, now) < keepMs) {
32493
32583
  continue;
32494
32584
  }
32495
32585
  try {
32496
- rmSync8(path);
32586
+ rmSync9(path);
32497
32587
  } catch {}
32498
32588
  }
32499
32589
  }
32500
32590
 
32591
+ // src/script-origins.ts
32592
+ import { join as join20 } from "node:path";
32593
+ var UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
32594
+ function unresolvedClassIn(message) {
32595
+ return UNRESOLVED.exec(message)?.[1] ?? null;
32596
+ }
32597
+ var MOST_CHECKED = 8;
32598
+ async function originsOf(errors, declared, check) {
32599
+ const listed = new Set(errors.map((error) => error.path));
32600
+ const origins = [];
32601
+ const unconfirmed = [];
32602
+ const asked = new Set;
32603
+ const waiting = errors.map((error) => unresolvedClassIn(error.message)).filter((name) => name !== null);
32604
+ while (waiting.length > 0 && asked.size < MOST_CHECKED) {
32605
+ const name = waiting.shift() ?? "";
32606
+ const path = declared.get(name);
32607
+ if (path === undefined || listed.has(path) || asked.has(path)) {
32608
+ continue;
32609
+ }
32610
+ asked.add(path);
32611
+ const checked = await check(path);
32612
+ const own = checked.errors.filter((error) => error.path === path);
32613
+ if (own.length === 0) {
32614
+ unconfirmed.push({
32615
+ path,
32616
+ declares: name,
32617
+ why: checked.failure === null ? "the engine's check of it found no error" : `the engine's check of it did not finish (${checked.failure})`
32618
+ });
32619
+ continue;
32620
+ }
32621
+ for (const error of own) {
32622
+ origins.push({ ...error, declares: name });
32623
+ const deeper = unresolvedClassIn(error.message);
32624
+ if (deeper !== null) {
32625
+ waiting.push(deeper);
32626
+ }
32627
+ }
32628
+ }
32629
+ return { origins, unconfirmed };
32630
+ }
32631
+ var RELOADED_AT = /GDScript::reload \((res:\/\/.+):(\d+)\)$/;
32632
+ function reloadedAt(entry) {
32633
+ for (const line of entry.detail) {
32634
+ const at = RELOADED_AT.exec(line.trim());
32635
+ if (at !== null) {
32636
+ return { path: at[1] ?? "", line: Number(at[2]) === 0 ? null : Number(at[2]) };
32637
+ }
32638
+ }
32639
+ return null;
32640
+ }
32641
+ function checkWithEngine(godotPath, projectPath) {
32642
+ return async (path) => {
32643
+ const logDir = scratchDirectory("tests");
32644
+ try {
32645
+ const ran = await runEngine(godotPath, [
32646
+ "--headless",
32647
+ "--log-file",
32648
+ join20(logDir, "engine.log"),
32649
+ "--path",
32650
+ projectPath,
32651
+ "--check-only",
32652
+ "-s",
32653
+ path
32654
+ ], { timeout: { ms: 60000, said: "minute" } });
32655
+ const errors = ran.log.select({ severity: "error", sinceLastCall: false, limit: 50 }).entries.flatMap((entry) => {
32656
+ const at = reloadedAt(entry);
32657
+ return at === null ? [] : [{ ...at, message: entry.text }];
32658
+ });
32659
+ return { errors, failure: ran.failure === null ? null : howItEnded(ran) };
32660
+ } finally {
32661
+ discard(logDir);
32662
+ }
32663
+ };
32664
+ }
32665
+ function withoutListed(entries, errors) {
32666
+ if (errors.length === 0) {
32667
+ return [...entries];
32668
+ }
32669
+ const listing = discoveryListing(entries.map((entry) => entry.text));
32670
+ const paths = new Set(errors.map((error) => error.path));
32671
+ return entries.filter((entry, at) => {
32672
+ if (listing !== null && at >= listing.from && at < listing.to) {
32673
+ return false;
32674
+ }
32675
+ const reloaded = reloadedAt(entry);
32676
+ if (reloaded !== null && errors.some((error) => error.path === reloaded.path && error.line === reloaded.line && error.message === entry.text)) {
32677
+ return false;
32678
+ }
32679
+ const failedToLoad = /^Failed to load script "(res:\/\/[^"]+)" with error "[^"]+"\.$/.exec(entry.text);
32680
+ return failedToLoad === null || !paths.has(failedToLoad[1] ?? "");
32681
+ });
32682
+ }
32683
+
32501
32684
  // src/setup.ts
32502
32685
  import { spawnSync } from "node:child_process";
32503
32686
  import { createHash as createHash4 } from "node:crypto";
32504
32687
  import {
32505
32688
  cpSync,
32506
- existsSync as existsSync15,
32507
- mkdirSync as mkdirSync11,
32689
+ existsSync as existsSync16,
32690
+ mkdirSync as mkdirSync12,
32508
32691
  readdirSync as readdirSync8,
32509
- readFileSync as readFileSync18,
32692
+ readFileSync as readFileSync19,
32510
32693
  rmdirSync,
32511
- rmSync as rmSync9,
32694
+ rmSync as rmSync10,
32512
32695
  statSync as statSync7,
32513
- writeFileSync as writeFileSync13
32696
+ writeFileSync as writeFileSync14
32514
32697
  } from "node:fs";
32515
- import { dirname as dirname13, join as join19 } from "node:path";
32698
+ import { dirname as dirname14, join as join21 } from "node:path";
32516
32699
  import { fileURLToPath as fileURLToPath3 } from "node:url";
32517
32700
  var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
32518
32701
  var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
@@ -32536,21 +32719,21 @@ function addonsDigest(names, from) {
32536
32719
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
32537
32720
  continue;
32538
32721
  }
32539
- const path = join19(directory, name);
32722
+ const path = join21(directory, name);
32540
32723
  const inside = `${relative}/${name}`;
32541
32724
  if (statSync7(path).isDirectory()) {
32542
32725
  walk(path, inside);
32543
32726
  } else {
32544
32727
  hash.update(`${inside}
32545
32728
  `);
32546
- hash.update(readFileSync18(path));
32729
+ hash.update(readFileSync19(path));
32547
32730
  hash.update(`
32548
32731
  `);
32549
32732
  }
32550
32733
  }
32551
32734
  };
32552
32735
  for (const name of names) {
32553
- walk(join19(from, name), name);
32736
+ walk(join21(from, name), name);
32554
32737
  }
32555
32738
  return hash.digest("hex");
32556
32739
  }
@@ -32583,22 +32766,22 @@ function installedRuntimeDigest(projectPath) {
32583
32766
  return digestMarker(projectPath, RUNTIME_ADDON);
32584
32767
  }
32585
32768
  function digestMarker(projectPath, addon) {
32586
- const marker = join19(projectPath, "addons", addon, DIGEST_MARKER);
32587
- if (!existsSync15(marker)) {
32769
+ const marker = join21(projectPath, "addons", addon, DIGEST_MARKER);
32770
+ if (!existsSync16(marker)) {
32588
32771
  return null;
32589
32772
  }
32590
- const said = readFileSync18(marker, "utf8").trim();
32773
+ const said = readFileSync19(marker, "utf8").trim();
32591
32774
  return said === "" ? null : said;
32592
32775
  }
32593
32776
  function shippedAddonsDirectory() {
32594
- return join19(dirname13(fileURLToPath3(import.meta.url)), "godot", "addons");
32777
+ return join21(dirname14(fileURLToPath3(import.meta.url)), "godot", "addons");
32595
32778
  }
32596
32779
  function installedAddonVersion(projectPath) {
32597
- const marker = join19(projectPath, "addons", ADDONS[0], VERSION_MARKER);
32598
- if (!existsSync15(marker)) {
32780
+ const marker = join21(projectPath, "addons", ADDONS[0], VERSION_MARKER);
32781
+ if (!existsSync16(marker)) {
32599
32782
  return null;
32600
32783
  }
32601
- const said = readFileSync18(marker, "utf8").trim();
32784
+ const said = readFileSync19(marker, "utf8").trim();
32602
32785
  return said === "" ? null : said;
32603
32786
  }
32604
32787
  function loaderAmong(projectPath, named) {
@@ -32609,7 +32792,7 @@ function loaderAmong(projectPath, named) {
32609
32792
  }
32610
32793
  let source = null;
32611
32794
  try {
32612
- source = readFileSync18(join19(projectPath, path), "utf8");
32795
+ source = readFileSync19(join21(projectPath, path), "utf8");
32613
32796
  } catch {}
32614
32797
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
32615
32798
  return { name: entry, path, exists: true };
@@ -32651,10 +32834,10 @@ function enabledPlugins(settings) {
32651
32834
  function inspectProject(projectPath) {
32652
32835
  const problems = [];
32653
32836
  const addons = ADDONS.map((name) => {
32654
- const directory = join19(projectPath, "addons", name);
32655
- const marker = join19(directory, VERSION_MARKER);
32656
- const installed = existsSync15(directory);
32657
- const version = existsSync15(marker) ? readFileSync18(marker, "utf8").trim() : null;
32837
+ const directory = join21(projectPath, "addons", name);
32838
+ const marker = join21(directory, VERSION_MARKER);
32839
+ const installed = existsSync16(directory);
32840
+ const version = existsSync16(marker) ? readFileSync19(marker, "utf8").trim() : null;
32658
32841
  const current = version === SERVER_VERSION;
32659
32842
  if (!installed) {
32660
32843
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -32663,7 +32846,7 @@ function inspectProject(projectPath) {
32663
32846
  }
32664
32847
  return { name, installed, version, current };
32665
32848
  });
32666
- const settings = parseProjectGodot(readFileSync18(join19(projectPath, "project.godot"), "utf8"));
32849
+ const settings = parseProjectGodot(readFileSync19(join21(projectPath, "project.godot"), "utf8"));
32667
32850
  const pluginsEnabled = enabledPlugins(settings);
32668
32851
  for (const name of EDITOR_PLUGINS) {
32669
32852
  if (!pluginsEnabled.includes(name)) {
@@ -32679,7 +32862,7 @@ function inspectProject(projectPath) {
32679
32862
  const tracked = trackedByGit(projectPath, [...named.values()]);
32680
32863
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
32681
32864
  for (const [name, path] of named) {
32682
- if (!existsSync15(join19(projectPath, path))) {
32865
+ if (!existsSync16(join21(projectPath, path))) {
32683
32866
  if (!addonMissing(path)) {
32684
32867
  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`);
32685
32868
  }
@@ -33085,7 +33268,7 @@ var TOOL_SPECS = [
33085
33268
  },
33086
33269
  {
33087
33270
  name: "project_test",
33088
- 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. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. 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. Needs gdUnit4 under addons/gdUnit4.",
33271
+ 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. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. Needs gdUnit4 under addons/gdUnit4.",
33089
33272
  parameters: {
33090
33273
  projectPath: PROJECT_PATH,
33091
33274
  path: {
@@ -33655,7 +33838,7 @@ var TOOL_SPECS = [
33655
33838
  says: {
33656
33839
  type: "string",
33657
33840
  ops: ["find"],
33658
- description: 'find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern, and a glob that finds nothing says under note how many nodes it would find open at both ends. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Alternatives separated by | match when any one of them does, each by these rules ("*won*|*lost*"); write \\| for a bar that is part of the words.'
33841
+ description: 'find: part of what the node has written on it as drawn, the way the text op reads it, case-insensitively and with a typed space matching any space the game draws (a non-breaking one included), which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern, and a glob that finds nothing says under note how many nodes it would find open at both ends. A label with a line break is matched with the break in the words, and a backslash followed by n counts as one. Alternatives separated by | match when any one of them does, each by these rules ("*won*|*lost*"); write \\| for a bar that is part of the words.'
33659
33842
  },
33660
33843
  limit: {
33661
33844
  type: "integer",
@@ -33833,7 +34016,7 @@ var TOOL_SPECS = [
33833
34016
  says: {
33834
34017
  type: "string",
33835
34018
  ops: ["click"],
33836
- description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it, and | separates alternatives), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it or a dialog stacked above its own, is not on screen; nor is one on a hidden canvas layer, one outside the viewport, or one a container clips away that a click does not scroll. The refusal counts those apart. The matches are ranked the way a person picks what to press: an enabled button before anything else, a disabled one included, then a control whose whole text, or one whole line of it such as the title on a card, is the words before one saying them as part of more, and the best one is pressed; found.picked says why. Several matches at the best rank are refused with each one listed, and index picks one. A match saying the words only as part of more is named under found.partOf, and is refused when a control saying exactly the words is hidden, off the screen or covered, since it would stand in for that one; index presses it anyway. An empty field says its placeholder. An item of an open menu is drawn by the menu rather than held as a control, so a click finds none, and the refusal names the menu and the choose call that picks it."
34019
+ description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it, and | separates alternatives), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it or a dialog stacked above its own, is not on screen; nor is one on a hidden canvas layer, one outside the viewport, or one a container clips away that a click does not scroll. The refusal counts those apart. The matches are ranked the way a person picks what to press: an enabled button before anything else, a disabled one included, then a control whose whole text, or one whole line of it such as the title on a card, is the words before one saying them as part of more, and the best one is pressed; found.picked says why. Several matches at the best rank are refused with each one listed, and index picks one. A match saying the words only as part of more is named under found.partOf, and is refused when another control says exactly the words (hidden, off the screen, covered, or on screen taking clicks without being a button), since it would stand in for that one, and when it lets the pointer through to what is under it; index presses it anyway. Words found only inside longer words (\"out\" in \"Scout\") rank after every other match and are refused unless index is given; words starting a longer word (\"Recruit\" in \"Recruits\") count. An empty field says its placeholder, and a control with no text of its own, a picture on a button, says its accessibility name. An item of an open menu is drawn by the menu rather than held as a control, so a click finds none, and the refusal names the menu and the choose call that picks it."
33837
34020
  },
33838
34021
  action: {
33839
34022
  type: "string",
@@ -33910,7 +34093,7 @@ var TOOL_SPECS = [
33910
34093
  requires: [],
33911
34094
  operations: {
33912
34095
  click: {
33913
- summary: `press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. The control is named by nodePath, or by says with the words on it, found under nodePath when both are given, and the answer then says under found which match it was. Words that are part of a RichTextLabel are pressed where they are drawn rather than at the label's middle, so a link among them is the one pressed: found.pressedOn says "the words", or "the middle of the label" with a note saying why they could not be placed, and link_pressed names the meta the label reports clicked. A control out of sight inside a ScrollContainer is scrolled to first, each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A disabled button, and a control that does not process (the game paused, say), are refused: the pointer reaches them and the press does nothing. A 3D node is clicked where it is drawn, and landed then 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`,
34096
+ summary: `press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. The control is named by nodePath, or by says with the words on it, found under nodePath when both are given, and the answer then says under found which match it was. Words that are part of a RichTextLabel are pressed where they are drawn rather than at the label's middle, so a link among them is the one pressed: found.pressedOn says "the words", or "the middle of the label" with a note saying why they could not be placed, and 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 out of sight inside a ScrollContainer is scrolled to first, each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A disabled button, and a control that does not process (the game paused, say), are refused: the pointer reaches them and the press does nothing. A 3D node is clicked where it is drawn, and landed then 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`,
33914
34097
  requires: []
33915
34098
  },
33916
34099
  choose: {
@@ -34089,7 +34272,7 @@ function buildToolDefinitions() {
34089
34272
  var UPDATE_NOTICE_EVERY = 500;
34090
34273
  var FEEDBACK_NOTICE_EVERY = 250;
34091
34274
  var run4 = promisify5(execFile5);
34092
- var __dirname2 = dirname14(fileURLToPath4(import.meta.url));
34275
+ var __dirname2 = dirname15(fileURLToPath4(import.meta.url));
34093
34276
  function editorRestartTimeoutMs() {
34094
34277
  const override = Number.parseInt(envValue("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS") ?? "", 10);
34095
34278
  return Number.isInteger(override) && override > 0 ? override : 90000;
@@ -34110,16 +34293,16 @@ function captureDestinationRefusal(outputPath, projects) {
34110
34293
  if (extname2(outputPath).toLowerCase() !== ".png") {
34111
34294
  return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
34112
34295
  }
34113
- const directory = realPathOr(dirname14(outputPath));
34114
- if (!existsSync16(directory)) {
34115
- return `outputPath's directory ${dirname14(outputPath)} does not exist; a capture creates no directories.`;
34296
+ const directory = realPathOr(dirname15(outputPath));
34297
+ if (!existsSync17(directory)) {
34298
+ return `outputPath's directory ${dirname15(outputPath)} does not exist; a capture creates no directories.`;
34116
34299
  }
34117
- const where = join20(directory, basename5(outputPath));
34300
+ const where = join22(directory, basename5(outputPath));
34118
34301
  const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
34119
34302
  if (inside !== undefined) {
34120
34303
  return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
34121
34304
  }
34122
- if (existsSync16(where)) {
34305
+ if (existsSync17(where)) {
34123
34306
  return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
34124
34307
  }
34125
34308
  return null;
@@ -34623,20 +34806,75 @@ function settingsNamedBy(op, args) {
34623
34806
  }
34624
34807
  }
34625
34808
  var STALLED_AFTER_MS = 30000;
34809
+ var TEST_HEARTBEAT_MS = 20000;
34626
34810
  var DEPENDED_FAILED = "Failed to compile depended scripts";
34627
34811
  function foldedScriptErrors(errors) {
34628
- const scriptErrors = errors.filter((error) => !error.message.includes(DEPENDED_FAILED));
34812
+ const unresolved = new Map;
34813
+ for (const error of errors) {
34814
+ const name = unresolvedClassIn(error.message);
34815
+ if (name !== null) {
34816
+ unresolved.set(error.path, (unresolved.get(error.path) ?? new Set).add(name));
34817
+ }
34818
+ }
34819
+ const follows = (error) => {
34820
+ const names = unresolved.get(error.path);
34821
+ if (names === undefined) {
34822
+ return false;
34823
+ }
34824
+ const reparsed = /Could not parse global class "([A-Za-z_][A-Za-z0-9_]*)"/.exec(error.message)?.[1];
34825
+ return error.message.includes('the inferred type "Variant"') || reparsed !== undefined && names.has(reparsed);
34826
+ };
34827
+ const scriptErrors = errors.filter((error) => !error.message.includes(DEPENDED_FAILED) && !follows(error));
34629
34828
  const own = new Set(scriptErrors.map((error) => error.path));
34630
34829
  return {
34631
34830
  scriptErrors,
34632
- failedThroughDependency: [...new Set(errors.map((error) => error.path))].filter((path) => !own.has(path))
34831
+ failedThroughDependency: [...new Set(errors.map((error) => error.path))].filter((path) => !own.has(path)),
34832
+ followFromUnresolved: errors.filter(follows)
34633
34833
  };
34634
34834
  }
34635
34835
  function nonEmpty(lists) {
34636
34836
  return Object.fromEntries(Object.entries(lists).filter(([, list]) => list.length > 0));
34637
34837
  }
34638
- function scriptErrorsNote(errors) {
34639
- const { scriptErrors: own, failedThroughDependency: dependents } = foldedScriptErrors(errors);
34838
+ function withPreviousCut(answer, previous) {
34839
+ const content = answer.content.map((part) => ({ ...part }));
34840
+ const last = content.at(-1);
34841
+ let json = null;
34842
+ try {
34843
+ const parsed = JSON.parse(last?.text ?? "");
34844
+ json = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { ...parsed } : null;
34845
+ } catch {}
34846
+ if (last !== undefined && json !== null) {
34847
+ last.text = answerJson({ ...json, previousRunCut: previous });
34848
+ }
34849
+ const note = cutTestRunNote(previous);
34850
+ const first = content[0];
34851
+ if (first === undefined || first === last && json !== null || typeof first.text !== "string") {
34852
+ content.unshift({ type: "text", text: note });
34853
+ } else {
34854
+ first.text = `${first.text} ${note}`;
34855
+ }
34856
+ return { ...answer, content };
34857
+ }
34858
+ function finishedBeforeTheEnd(finished, failing, running) {
34859
+ if (finished === 0 && running === null) {
34860
+ return "";
34861
+ }
34862
+ const got = finished === 0 ? "no suite had finished" : `${finished === 1 ? "one suite" : `${finished} suites`} finished${failing === 0 ? "" : finished === 1 ? ", with failures" : `, ${failing} of them with failures`}`;
34863
+ const listed = finished === 0 ? "" : `; ${finished === 1 ? "it is" : "they are"} under suitesFinished${failing === 0 ? "" : ", with the failing cases under suitesFailing"}, so a run of the rest need not repeat ${finished === 1 ? "it" : "them"}`;
34864
+ return ` Before it ended, ${got}${running === null ? "" : `${finished === 0 ? " and" : ", and"} ${running} was running`}${listed}.`;
34865
+ }
34866
+ function scriptErrorsNote(errors, {
34867
+ origins,
34868
+ unconfirmed
34869
+ } = {
34870
+ origins: [],
34871
+ unconfirmed: []
34872
+ }) {
34873
+ const {
34874
+ scriptErrors: own,
34875
+ failedThroughDependency: dependents,
34876
+ followFromUnresolved: consequences
34877
+ } = foldedScriptErrors(errors);
34640
34878
  const causes = new Map;
34641
34879
  for (const error of own) {
34642
34880
  if (!causes.has(error.path)) {
@@ -34644,16 +34882,29 @@ function scriptErrorsNote(errors) {
34644
34882
  }
34645
34883
  }
34646
34884
  const scripts = causes.size + dependents.length;
34647
- const named = [...causes.values()].map((error) => `${error.line === null ? error.path : `${error.path}:${error.line}`} ${error.message}${/[.!?]\)?$/.test(error.message) ? "" : "."}`).join(" ");
34885
+ const at = (error) => error.line === null ? error.path : `${error.path}:${error.line}`;
34886
+ const sentence = (text) => `${text}${/[.!?]\)?$/.test(text) ? "" : "."}`;
34887
+ const traced = new Set;
34888
+ const failedOn = (declares) => {
34889
+ const through = [...causes.values()].filter((error) => unresolvedClassIn(error.message) === declares);
34890
+ for (const error of through) {
34891
+ traced.add(error.path);
34892
+ }
34893
+ return through.length === 0 ? "" : ` ${through.map(at).join(", ")} could not resolve ${declares} because of it.`;
34894
+ };
34895
+ const first = [...new Map(origins.map((origin) => [origin.path, origin])).values()].sort((a, b) => Number(unresolvedClassIn(a.message) !== null) - Number(unresolvedClassIn(b.message) !== null));
34896
+ const named = [
34897
+ ...first.map((origin) => `${at(origin)} ${sentence(origin.message)} gdUnit4 did not name this script, which declares ${origin.declares}; the engine's own check of it found the error.${failedOn(origin.declares)}`),
34898
+ ...unconfirmed.map((origin) => `${origin.path} declares ${origin.declares}, which gdUnit4 did not name, and ${origin.why}.${failedOn(origin.declares)}`),
34899
+ ...[...causes.values()].filter((error) => !traced.has(error.path)).map((error) => `${at(error)} ${sentence(error.message)}`)
34900
+ ].join(" ");
34648
34901
  const following = dependents.length === 0 ? "" : causes.size === 0 ? `${dependents.join(", ")} ${dependents.length === 1 ? "depends" : "depend"} on a script that did not compile, and gdUnit4 named none with an error of its own: script_diagnostics on one of them finds it.` : `${dependents.length === 1 ? "One more" : `${dependents.length} more`} failed only because a script ${dependents.length === 1 ? "it depends" : "they depend"} on did: ${dependents.join(", ")}.`;
34649
34902
  const more = own.length - causes.size;
34650
34903
  const rest = more > 0 ? `${more === 1 ? "One more error" : `${more} more errors`} in ${causes.size === 1 ? "that script" : "those scripts"} ${more === 1 ? "is" : "are"} under scriptErrors.` : "";
34651
- return [
34652
- `No tests ran: gdUnit4 could not load ${scripts === 1 ? "one script" : `${scripts} scripts`} while looking for suites, and it runs no suite at all when one fails to load.`,
34653
- named,
34654
- following,
34655
- rest
34656
- ].filter((part) => part !== "").join(" ");
34904
+ const after = consequences.length > 0 ? `${consequences.length === 1 ? "One more error comes" : `${consequences.length} more errors come`} only from a class that did not resolve, which the engine reads as Variant, and ${consequences.length === 1 ? "is" : "are"} under followFromUnresolved.` : "";
34905
+ const unnamed = first.length;
34906
+ const opening = unnamed === 0 ? `No tests ran: gdUnit4 could not load ${scripts === 1 ? "one script" : `${scripts} scripts`} while looking for suites, and it runs no suite at all when one fails to load.` : `No tests ran: ${scripts + unnamed} scripts did not compile, and gdUnit4 runs no suite at all when one fails to load. It could not load ${scripts === 1 ? "one" : scripts} of them while looking for suites, and ${unnamed === 1 ? "the one it did not name comes" : `the ${unnamed} it did not name come`} first.`;
34907
+ return [opening, named, following, rest, after].filter((part) => part !== "").join(" ");
34657
34908
  }
34658
34909
  function timedOutVerdict(timeoutMs, silentForMs) {
34659
34910
  const quiet = Math.round(silentForMs);
@@ -34743,7 +34994,7 @@ function aboveTheRunner(entry) {
34743
34994
  }
34744
34995
  function elsewhereIn(projectPath, asked) {
34745
34996
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
34746
- if (wanted === "" || existsSync16(join20(projectPath, wanted))) {
34997
+ if (wanted === "" || existsSync17(join22(projectPath, wanted))) {
34747
34998
  return "";
34748
34999
  }
34749
35000
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -34787,7 +35038,7 @@ function testPathsIn(projectPath, given) {
34787
35038
  };
34788
35039
  }
34789
35040
  function hasMainScene(projectFile) {
34790
- const scene = parseProjectGodot(readFileSync19(projectFile, "utf8"))["application"]?.["run/main_scene"];
35041
+ const scene = parseProjectGodot(readFileSync20(projectFile, "utf8"))["application"]?.["run/main_scene"];
34791
35042
  return typeof scene === "string" && scene !== "";
34792
35043
  }
34793
35044
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -34795,13 +35046,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
34795
35046
  return false;
34796
35047
  }
34797
35048
  try {
34798
- return readFileSync19(join20(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
35049
+ return readFileSync20(join22(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
34799
35050
  } catch {
34800
35051
  return false;
34801
35052
  }
34802
35053
  }
34803
35054
  function editorPlaysHeadless(projectFile) {
34804
- const runArgs = parseProjectGodot(readFileSync19(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
35055
+ const runArgs = parseProjectGodot(readFileSync20(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
34805
35056
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
34806
35057
  }
34807
35058
  function camelCased(params) {
@@ -34820,7 +35071,7 @@ function needsItsOwnProcess(asked) {
34820
35071
  class GodotServer {
34821
35072
  mcp;
34822
35073
  locator = new GodotLocator;
34823
- operationsScript = join20(__dirname2, "godot", "operations", "godot_operations.gd");
35074
+ operationsScript = join22(__dirname2, "godot", "operations", "godot_operations.gd");
34824
35075
  godotBridge;
34825
35076
  tools = buildToolDefinitions();
34826
35077
  activeProcess = null;
@@ -35123,11 +35374,20 @@ class GodotServer {
35123
35374
  return checked.response;
35124
35375
  }
35125
35376
  const named = args["projectPath"];
35126
- if (typeof named === "string" && existsSync16(join20(named, "project.godot"))) {
35377
+ if (typeof named === "string" && existsSync17(join22(named, "project.godot"))) {
35127
35378
  this.lastProjectPath = named;
35128
35379
  }
35129
35380
  this.updates.refresh();
35130
- const answer = await withCallSignal(extra.signal, () => this.answered(spec.name, checked.op ?? "", args));
35381
+ const token = request.params._meta?.progressToken;
35382
+ const progress = token === undefined ? undefined : (done, message) => {
35383
+ extra.sendNotification({
35384
+ method: "notifications/progress",
35385
+ params: { progressToken: token, progress: done, message }
35386
+ }).catch((error) => {
35387
+ this.logDebug(`Progress for ${spec.name} was not sent: ${errorMessage(error)}`);
35388
+ });
35389
+ };
35390
+ const answer = await withCallSignal(extra.signal, () => this.answered(spec.name, checked.op ?? "", args), progress);
35131
35391
  return this.withFeedbackNotice(this.withUpdateNotice(answer));
35132
35392
  });
35133
35393
  }
@@ -35597,8 +35857,8 @@ class GodotServer {
35597
35857
  if (path === undefined) {
35598
35858
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
35599
35859
  }
35600
- const file = join20(path, "project.godot");
35601
- if (!existsSync16(file)) {
35860
+ const file = join22(path, "project.godot");
35861
+ if (!existsSync17(file)) {
35602
35862
  return {
35603
35863
  ok: false,
35604
35864
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -36209,7 +36469,7 @@ class GodotServer {
36209
36469
  if (!engine.ok) {
36210
36470
  return engine.response;
36211
36471
  }
36212
- const application = parseProjectGodot(readFileSync19(project.value.file, "utf8"))["application"] ?? {};
36472
+ const application = parseProjectGodot(readFileSync20(project.value.file, "utf8"))["application"] ?? {};
36213
36473
  const name = application["config/name"];
36214
36474
  const mainScene = application["run/main_scene"];
36215
36475
  const info = {
@@ -36265,18 +36525,18 @@ class GodotServer {
36265
36525
  if (!engine.ok) {
36266
36526
  return engine.response;
36267
36527
  }
36268
- const folder = dirname14(output.absolutePath);
36528
+ const folder = dirname15(output.absolutePath);
36269
36529
  try {
36270
- mkdirSync12(folder, { recursive: true });
36530
+ mkdirSync13(folder, { recursive: true });
36271
36531
  } catch (error) {
36272
- return this.createErrorResponse(`Cannot create ${dirname14(output.relativePath)} to export into: ${errorMessage(error)}`);
36532
+ return this.createErrorResponse(`Cannot create ${dirname15(output.relativePath)} to export into: ${errorMessage(error)}`);
36273
36533
  }
36274
36534
  const debug = readBoolean(args, "debug") ?? false;
36275
36535
  const exportLogs = scratchDirectory("export");
36276
36536
  const exportArgs = [
36277
36537
  "--headless",
36278
36538
  "--log-file",
36279
- join20(exportLogs, "engine.log"),
36539
+ join22(exportLogs, "engine.log"),
36280
36540
  "--path",
36281
36541
  project.value.path,
36282
36542
  debug ? "--export-debug" : "--export-release",
@@ -36284,7 +36544,7 @@ class GodotServer {
36284
36544
  output.absolutePath
36285
36545
  ];
36286
36546
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
36287
- const writtenAt = () => existsSync16(output.absolutePath) ? statSync8(output.absolutePath).mtimeMs : null;
36547
+ const writtenAt = () => existsSync17(output.absolutePath) ? statSync8(output.absolutePath).mtimeMs : null;
36288
36548
  const before = writtenAt();
36289
36549
  let ending;
36290
36550
  try {
@@ -36297,7 +36557,38 @@ class GodotServer {
36297
36557
  }
36298
36558
  return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt());
36299
36559
  }
36560
+ reportTestProgress(log) {
36561
+ const progress = callProgress();
36562
+ if (progress === undefined) {
36563
+ return;
36564
+ }
36565
+ const began = Date.now();
36566
+ let read = 0;
36567
+ let lastSent = began;
36568
+ const ticker = setInterval(() => {
36569
+ const entries = log.all;
36570
+ const finishedOne = entries.slice(read).some((entry) => finishesASuite(entry.text));
36571
+ read = entries.length;
36572
+ const now = Date.now();
36573
+ if (!finishedOne && now - lastSent < TEST_HEARTBEAT_MS) {
36574
+ return;
36575
+ }
36576
+ lastSent = now;
36577
+ progress(Math.floor((now - began) / 1000), testProgressMessage(entries.map((entry) => entry.text), now - began));
36578
+ }, 1000);
36579
+ ticker.unref();
36580
+ return ticker;
36581
+ }
36300
36582
  async handleRunTests(args) {
36583
+ const answer = await this.runTests(args);
36584
+ const project = this.project(args);
36585
+ if (!project.ok || callSignal()?.aborted === true) {
36586
+ return answer;
36587
+ }
36588
+ const previous = takeCutTestRun(project.value.path);
36589
+ return previous === null ? answer : withPreviousCut(answer, previous);
36590
+ }
36591
+ async runTests(args) {
36301
36592
  const project = this.project(args);
36302
36593
  if (!project.ok) {
36303
36594
  return project.response;
@@ -36306,7 +36597,7 @@ class GodotServer {
36306
36597
  if (!paths.ok) {
36307
36598
  return this.createErrorResponse(paths.reason, PATH_SOLUTIONS);
36308
36599
  }
36309
- const missing = paths.value.filter((path) => !existsSync16(join20(project.value.path, path.slice("res://".length))));
36600
+ const missing = paths.value.filter((path) => !existsSync17(join22(project.value.path, path.slice("res://".length))));
36310
36601
  const asked = paths.value.filter((path) => !missing.includes(path));
36311
36602
  if (asked.length === 0) {
36312
36603
  const verdict = `nothing at ${missing.join(" and ")}`;
@@ -36322,7 +36613,7 @@ class GodotServer {
36322
36613
  };
36323
36614
  }
36324
36615
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
36325
- if (!existsSync16(join20(project.value.path, runner))) {
36616
+ if (!existsSync17(join22(project.value.path, runner))) {
36326
36617
  return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
36327
36618
  "Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
36328
36619
  ]);
@@ -36367,6 +36658,7 @@ class GodotServer {
36367
36658
  run.process.stderr?.on("data", () => {
36368
36659
  lastSaid = Date.now();
36369
36660
  });
36661
+ const reporting = this.reportTestProgress(run.log);
36370
36662
  let silentForMs = 0;
36371
36663
  const timedOut = await new Promise((resolve) => {
36372
36664
  const timer = setTimeout(() => {
@@ -36383,25 +36675,26 @@ class GodotServer {
36383
36675
  resolve(false);
36384
36676
  });
36385
36677
  });
36678
+ clearInterval(reporting);
36386
36679
  if (timedOut && run.process.pid !== undefined) {
36387
36680
  await untilGone([run.process.pid]);
36388
36681
  }
36389
36682
  await run.settled;
36390
- const reportsDir = join20(project.value.path, ".godot", ...ours.split("/"));
36683
+ const reportsDir = join22(project.value.path, ".godot", ...ours.split("/"));
36391
36684
  let report = null;
36392
36685
  let reportProblem = null;
36393
36686
  try {
36394
- const written = existsSync16(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
36687
+ const written = existsSync17(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
36395
36688
  const newest = written.at(-1);
36396
36689
  if (newest !== undefined) {
36397
- report = parseJUnit(readFileSync19(join20(reportsDir, newest, "results.xml"), "utf8"));
36690
+ report = parseJUnit(readFileSync20(join22(reportsDir, newest, "results.xml"), "utf8"));
36398
36691
  }
36399
36692
  } catch (error) {
36400
36693
  reportProblem = errorMessage(error);
36401
36694
  } finally {
36402
36695
  discard(reportsDir, userData);
36403
36696
  try {
36404
- rmdirSync2(dirname14(reportsDir));
36697
+ rmdirSync2(dirname15(reportsDir));
36405
36698
  } catch {}
36406
36699
  }
36407
36700
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
@@ -36423,7 +36716,9 @@ class GodotServer {
36423
36716
  const nothingRan = timedOut ? null : whyNoReport(said, asked.join(" and "));
36424
36717
  const cutShort = timedOut ? timedOutVerdict(timeoutMs, silentForMs) : null;
36425
36718
  const hung = cutShort?.hung ?? false;
36426
- const scriptErrors = timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
36719
+ const printedErrors = timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
36720
+ const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
36721
+ const scriptErrors = [...declaring.origins, ...printedErrors];
36427
36722
  const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
36428
36723
  const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
36429
36724
  const nothingAt = missing.join(" and ");
@@ -36436,9 +36731,28 @@ class GodotServer {
36436
36731
  printed: echoed.entries.map((entry) => entry.text),
36437
36732
  ...echoed.omitted > 0 ? { printedOmitted: echoed.omitted } : {}
36438
36733
  };
36734
+ const sofar = report === null ? suitesPrinted(said) : null;
36439
36735
  if (report === null) {
36440
- const said = nothingRan !== null ? `No tests ran: ${ranAs}.` : scriptErrors.length > 0 ? scriptErrorsNote(scriptErrors) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${ranAs}.` : `The test run wrote no report (${ranAs}).`;
36736
+ const finished = sofar?.finished ?? [];
36737
+ const running = sofar?.running ?? null;
36738
+ const failing = finished.filter((suite) => suite.errors + suite.failures > 0);
36739
+ const atTheEnd = {
36740
+ ...nonEmpty({
36741
+ suitesFinished: finished.map((suite) => suite.path),
36742
+ suitesFailing: failing.map(({ path, errors, failures, failed }) => ({
36743
+ path,
36744
+ errors,
36745
+ failures,
36746
+ failed
36747
+ }))
36748
+ }),
36749
+ ...running === null ? {} : { suiteRunning: running }
36750
+ };
36751
+ const said = nothingRan !== null ? `No tests ran: ${ranAs}.` : scriptErrors.length > 0 ? scriptErrorsNote(printedErrors, declaring) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${ranAs}.` : `The test run wrote no report (${ranAs}).${finishedBeforeTheEnd(finished.length, failing.length, running)}`;
36441
36752
  const note = missingNote === undefined ? said : `${said} ${missingNote}`;
36753
+ if (callSignal()?.aborted === true) {
36754
+ keepCutTestRun(project.value.path, { cutAt: new Date().toISOString(), paths: asked, ...atTheEnd });
36755
+ }
36442
36756
  return {
36443
36757
  content: [
36444
36758
  { type: "text", text: note },
@@ -36454,8 +36768,9 @@ class GodotServer {
36454
36768
  ...timedOut ? { timedOut, silentForMs } : {},
36455
36769
  ...missing.length > 0 ? { missing } : {},
36456
36770
  ...nonEmpty(foldedScriptErrors(scriptErrors)),
36771
+ ...atTheEnd,
36457
36772
  arguments: cmdArgs,
36458
- entries: forAnswer(printed.slice(0, 60).map(aboveTheRunner)),
36773
+ entries: forAnswer(withoutListed(printed, scriptErrors).slice(0, 60).map(aboveTheRunner)),
36459
36774
  ...printedLines,
36460
36775
  savesNote,
36461
36776
  ...scanned
@@ -36588,11 +36903,11 @@ class GodotServer {
36588
36903
  const cached = cachedClasses(projectPath);
36589
36904
  const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
36590
36905
  const contained = resolveWithinProject(projectPath, resourcePath);
36591
- if (!contained.ok || !existsSync16(contained.absolutePath)) {
36906
+ if (!contained.ok || !existsSync17(contained.absolutePath)) {
36592
36907
  return null;
36593
36908
  }
36594
36909
  try {
36595
- return readFileSync19(contained.absolutePath, "utf8");
36910
+ return readFileSync20(contained.absolutePath, "utf8");
36596
36911
  } catch {
36597
36912
  return null;
36598
36913
  }
@@ -36648,11 +36963,11 @@ class GodotServer {
36648
36963
  }
36649
36964
  sourceOfClass(projectPath, resourcePath) {
36650
36965
  const contained = resolveWithinProject(projectPath, resourcePath);
36651
- if (!contained.ok || !existsSync16(contained.absolutePath)) {
36966
+ if (!contained.ok || !existsSync17(contained.absolutePath)) {
36652
36967
  return null;
36653
36968
  }
36654
36969
  try {
36655
- return readFileSync19(contained.absolutePath, "utf8");
36970
+ return readFileSync20(contained.absolutePath, "utf8");
36656
36971
  } catch {
36657
36972
  return null;
36658
36973
  }
@@ -36963,7 +37278,7 @@ class GodotServer {
36963
37278
  return null;
36964
37279
  }
36965
37280
  try {
36966
- return settingKeys(readFileSync19(join20(projectPath, "project.godot"), "utf8"));
37281
+ return settingKeys(readFileSync20(join22(projectPath, "project.godot"), "utf8"));
36967
37282
  } catch {
36968
37283
  return null;
36969
37284
  }
@@ -37285,7 +37600,7 @@ class GodotServer {
37285
37600
  }
37286
37601
  async runtimeUp(projectPath, before, budget, withArgs) {
37287
37602
  const budgetMs = budget.ms;
37288
- if (!existsSync16(join20(projectPath, RUNTIME_AUTOLOAD.path))) {
37603
+ if (!existsSync17(join22(projectPath, RUNTIME_AUTOLOAD.path))) {
37289
37604
  return runtimeVerdict(null, {
37290
37605
  addon: false,
37291
37606
  budgetMs,
@@ -37544,7 +37859,7 @@ class GodotServer {
37544
37859
  return 0;
37545
37860
  }
37546
37861
  try {
37547
- return reportAlreadyInTranscript(readFileSync19(game.transcript), readFileSync19(report));
37862
+ return reportAlreadyInTranscript(readFileSync20(game.transcript), readFileSync20(report));
37548
37863
  } catch (error) {
37549
37864
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
37550
37865
  return 0;
@@ -38302,7 +38617,7 @@ class GodotServer {
38302
38617
  notes.push(endedWithoutACode({
38303
38618
  throughEditor: run.throughEditor,
38304
38619
  announced: announced !== undefined || run.announcedPid !== undefined,
38305
- couldAnnounce: run.projectPath !== null && existsSync16(join20(run.projectPath, RUNTIME_AUTOLOAD.path)),
38620
+ couldAnnounce: run.projectPath !== null && existsSync17(join22(run.projectPath, RUNTIME_AUTOLOAD.path)),
38306
38621
  endedHere: typeof run.endedHere === "string",
38307
38622
  pickedUp: run.pickedUp === true
38308
38623
  }));
@@ -38863,7 +39178,7 @@ class GodotServer {
38863
39178
  if (!scriptPath.ok) {
38864
39179
  return this.createErrorResponse(scriptPath.reason);
38865
39180
  }
38866
- if (!existsSync16(scriptPath.absolutePath)) {
39181
+ if (!existsSync17(scriptPath.absolutePath)) {
38867
39182
  return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
38868
39183
  }
38869
39184
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -39151,7 +39466,7 @@ class GodotServer {
39151
39466
  async ownRunOnItsWay(projectPath, budgetMs) {
39152
39467
  const run = await this.currentRun();
39153
39468
  const project = projectPath ?? run?.projectPath ?? null;
39154
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync16(join20(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
39469
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync17(join22(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
39155
39470
  return null;
39156
39471
  }
39157
39472
  const already = runtimesAnnounced().running;
@@ -39235,7 +39550,7 @@ class GodotServer {
39235
39550
  }
39236
39551
  }
39237
39552
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
39238
- const screenshotPath = screenshotDir ? join20(screenshotDir, "capture.png") : null;
39553
+ const screenshotPath = screenshotDir ? join22(screenshotDir, "capture.png") : null;
39239
39554
  const staleNote = this.runtimeStaleNote(choice.endpoint);
39240
39555
  const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
39241
39556
  const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
@@ -39284,7 +39599,7 @@ ${staleNote}`);
39284
39599
 
39285
39600
  ${staleNote}`}`
39286
39601
  },
39287
- { type: "image", data: readFileSync19(screenshotPath).toString("base64"), mimeType: "image/png" }
39602
+ { type: "image", data: readFileSync20(screenshotPath).toString("base64"), mimeType: "image/png" }
39288
39603
  ]
39289
39604
  };
39290
39605
  } finally {