gdharness 1.3.44 → 1.3.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js CHANGED
@@ -68,6 +68,124 @@ var init_errors = __esm(() => {
68
68
  };
69
69
  });
70
70
 
71
+ // src/paths.ts
72
+ import { realpathSync, statSync } from "node:fs";
73
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
74
+ function realAsFarAsItExists(path) {
75
+ const missing = [];
76
+ let at = path;
77
+ for (;; ) {
78
+ try {
79
+ return join(realpathSync.native(at), ...missing);
80
+ } catch {
81
+ const parent = dirname(at);
82
+ if (parent === at) {
83
+ return path;
84
+ }
85
+ missing.unshift(basename(at));
86
+ at = parent;
87
+ }
88
+ }
89
+ }
90
+ function isSameDirectory(onePath, otherPath) {
91
+ return relative(realAsFarAsItExists(resolve(onePath)), realAsFarAsItExists(resolve(otherPath))) === "";
92
+ }
93
+ function isWithinRoot(rootPath, candidatePath) {
94
+ const step = relative(resolve(rootPath), resolve(candidatePath));
95
+ return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
96
+ }
97
+ function keptInside(given) {
98
+ const at = given.indexOf("::");
99
+ const inner = at === -1 ? "" : given.slice(at + 2);
100
+ return KEPT_ID.test(inner) ? { file: given.slice(0, at), inner } : { file: given, inner: undefined };
101
+ }
102
+ function resolveWithinProject(projectPath, candidatePath, options = {}) {
103
+ const trimmed = candidatePath.trim();
104
+ if (trimmed === "") {
105
+ return { ok: false, reason: "Path is empty." };
106
+ }
107
+ if (trimmed.includes("\x00")) {
108
+ return { ok: false, reason: "Path contains a null byte." };
109
+ }
110
+ const root = resolve(projectPath);
111
+ const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : isAbsolute(trimmed) && isWithinRoot(root, trimmed) ? relative(root, resolve(trimmed)).split(sep).join("/") : trimmed;
112
+ if (withoutScheme.startsWith("uid://")) {
113
+ return {
114
+ ok: false,
115
+ reason: `Path '${trimmed}' is a UID, and a file is judged here by where it is on disk, which a UID does not say. Give the file's res:// path.`
116
+ };
117
+ }
118
+ const scheme = OTHER_SCHEME.exec(withoutScheme);
119
+ if (scheme) {
120
+ const elsewhere = scheme[0].toLowerCase() === "user://" ? " A game's user:// is a directory of its own outside the project: a running game's is under userDir in editor_status runtimes and in editor_run start's runtime, where a file reader of your own reads it." : "";
121
+ return {
122
+ ok: false,
123
+ reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.${elsewhere}`
124
+ };
125
+ }
126
+ if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
127
+ const tail = withoutScheme.split(/[\\/]+/).filter((step) => step !== "" && !step.endsWith(":"));
128
+ const besideIt = isAbsolute(trimmed) && isWithinRoot(realAsFarAsItExists(dirname(resolve(trimmed))), realAsFarAsItExists(root));
129
+ const example = tail.slice(besideIt ? -1 : -2).join("/") || "scenes/main.tscn";
130
+ return {
131
+ ok: false,
132
+ reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as '${example}'.`
133
+ };
134
+ }
135
+ const kept = keptInside(trimmed);
136
+ if (kept.inner !== undefined) {
137
+ return {
138
+ ok: false,
139
+ reason: `Path '${trimmed}' names the resource ${kept.inner} kept inside ${kept.file}, not a file. resource_edit read reads one by that path; this argument takes a file.`
140
+ };
141
+ }
142
+ if (withoutScheme.includes(":")) {
143
+ return {
144
+ ok: false,
145
+ reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
146
+ };
147
+ }
148
+ const absolutePath = resolve(root, withoutScheme);
149
+ if (!isWithinRoot(root, absolutePath)) {
150
+ return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
151
+ }
152
+ const relativePath = relative(root, absolutePath).split(sep).join("/");
153
+ if (relativePath === "" && options.root !== true) {
154
+ return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
155
+ }
156
+ return { ok: true, absolutePath, relativePath };
157
+ }
158
+ function exportedBuild(projectPath, candidatePath) {
159
+ const found = resolveWithinProject(projectPath, candidatePath);
160
+ if (!found.ok) {
161
+ return found;
162
+ }
163
+ let real;
164
+ try {
165
+ real = realpathSync.native(found.absolutePath);
166
+ } catch {
167
+ return {
168
+ ok: false,
169
+ reason: `There is no ${found.relativePath} in the project to start: export the build first, with project_export run.`
170
+ };
171
+ }
172
+ if (!isWithinRoot(realAsFarAsItExists(projectPath), real)) {
173
+ return {
174
+ ok: false,
175
+ reason: `${found.relativePath} leads outside the project, to ${real}, so it is not started.`
176
+ };
177
+ }
178
+ if (!statSync(real).isFile()) {
179
+ return { ok: false, reason: `${found.relativePath} is not a file, so there is nothing to start.` };
180
+ }
181
+ return { ok: true, absolutePath: real, relativePath: found.relativePath };
182
+ }
183
+ var RESOURCE_SCHEME = "res://", OTHER_SCHEME, KEPT_ID;
184
+ var init_paths = __esm(() => {
185
+ OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
186
+ KEPT_ID = /^[A-Za-z0-9_]+$/;
187
+ });
188
+
71
189
  // src/spawn.ts
72
190
  import {
73
191
  execFile,
@@ -243,7 +361,7 @@ var init_windows_ask = __esm(() => {
243
361
 
244
362
  // src/process-children.ts
245
363
  import { readFileSync } from "node:fs";
246
- import { basename } from "node:path";
364
+ import { basename as basename2 } from "node:path";
247
365
  async function startTimesOf(pids) {
248
366
  const began = new Map;
249
367
  if (pids.length === 0) {
@@ -345,7 +463,7 @@ function listingFailure(error) {
345
463
  const failed = error;
346
464
  const how = failed.killed === true ? `was killed after the ${ASK_TIMEOUT_MS}ms budget${failed.signal ? ` (${failed.signal})` : ""}` : failed.signal ? `was ended by ${failed.signal}` : failed.code !== undefined && failed.code !== null ? `exited with ${failed.code}` : "failed";
347
465
  const said = typeof failed.stderr === "string" ? failed.stderr.trim() : "";
348
- const asked = basename((failed.cmd ?? "").split(" ")[0] ?? "") || "the listing";
466
+ const asked = basename2((failed.cmd ?? "").split(" ")[0] ?? "") || "the listing";
349
467
  return `${asked} ${how}${said === "" ? ", with nothing on stderr" : `; stderr: ${said.slice(0, 400)}`}`;
350
468
  }
351
469
  function whyTheProcessTreeFailed() {
@@ -440,11 +558,21 @@ function ancestorsIn(tree, pid, root) {
440
558
  }
441
559
  return chain;
442
560
  }
561
+ function editorsOfProjectIn(tree, projectPath) {
562
+ const found = [];
563
+ for (const [pid, listed] of tree) {
564
+ const read = readCommandLine(listed.command);
565
+ if (read.editor && read.oneShot === null && read.projectPath !== null && /godot/i.test(read.executable) && !CONSOLE_WRAPPER.test(read.executable) && isSameDirectory(read.projectPath, projectPath)) {
566
+ found.push(pid);
567
+ }
568
+ }
569
+ return found.sort((a, b) => a - b);
570
+ }
443
571
  function readCommandLine(command) {
444
572
  const all = commandWords(command);
445
573
  const separator = all.findIndex((word) => word === "--" || word === "++");
446
574
  const words = separator === -1 ? all : all.slice(0, separator);
447
- const executable = words[0] === undefined ? "" : basename(words[0].replaceAll("\\", "/"));
575
+ const executable = words[0] === undefined ? "" : basename2(words[0].replaceAll("\\", "/"));
448
576
  const at = words.indexOf("--path");
449
577
  let projectPath = null;
450
578
  if (at !== -1 && at + 1 < words.length) {
@@ -507,8 +635,9 @@ async function askPosix() {
507
635
  });
508
636
  return stdout;
509
637
  }
510
- var ticksAnswered, ASK_TIMEOUT_MS = 15000, lastListingFailure, ONE_SHOT_OPTIONS;
638
+ var ticksAnswered, ASK_TIMEOUT_MS = 15000, lastListingFailure, ONE_SHOT_OPTIONS, CONSOLE_WRAPPER;
511
639
  var init_process_children = __esm(() => {
640
+ init_paths();
512
641
  init_spawn();
513
642
  init_windows_ask();
514
643
  ONE_SHOT_OPTIONS = [
@@ -532,13 +661,14 @@ var init_process_children = __esm(() => {
532
661
  "--convert-3to4",
533
662
  "--validate-conversion-3to4"
534
663
  ];
664
+ CONSOLE_WRAPPER = /_console(\.exe)?$/i;
535
665
  });
536
666
 
537
667
  // src/bridge-announce.ts
538
668
  import { mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "node:fs";
539
- import { dirname, join } from "node:path";
669
+ import { dirname as dirname2, join as join2 } from "node:path";
540
670
  function announcementPath(projectPath) {
541
- return join(projectPath, ".godot", "gdharness-bridge.json");
671
+ return join2(projectPath, ".godot", "gdharness-bridge.json");
542
672
  }
543
673
  function readAnnouncement(path) {
544
674
  try {
@@ -585,7 +715,7 @@ function announcementIsOurs(path) {
585
715
  function announceBridge(projectPath, bridge) {
586
716
  const path = announcementPath(projectPath);
587
717
  try {
588
- mkdirSync(dirname(path), { recursive: true });
718
+ mkdirSync(dirname2(path), { recursive: true });
589
719
  const announcement = {
590
720
  protocol: BRIDGE_ANNOUNCE_PROTOCOL,
591
721
  host: bridge.host,
@@ -652,15 +782,15 @@ import {
652
782
  openSync,
653
783
  readFileSync as readFileSync3,
654
784
  rmSync as rmSync2,
655
- statSync,
785
+ statSync as statSync2,
656
786
  writeFileSync as writeFileSync2,
657
787
  writeSync
658
788
  } from "node:fs";
659
789
  import { tmpdir as tmpdir2 } from "node:os";
660
- import { join as join2 } from "node:path";
790
+ import { join as join3 } from "node:path";
661
791
  import { setTimeout as delay } from "node:timers/promises";
662
792
  function helperAssembly(directory = tmpdir2()) {
663
- return join2(directory, `gdharness-desktop-${DIGEST}.dll`);
793
+ return join3(directory, `gdharness-desktop-${DIGEST}.dll`);
664
794
  }
665
795
  async function ensureHelperCompiled(directory = tmpdir2(), budgetMs = HELPER_COMPILE_MS, wait = true) {
666
796
  const assembly = helperAssembly(directory);
@@ -686,7 +816,7 @@ async function ensureHelperCompiled(directory = tmpdir2(), budgetMs = HELPER_COM
686
816
  }
687
817
  try {
688
818
  const holder = Number(readFileSync3(lock, "utf8").trim());
689
- const old = Date.now() - statSync(lock).mtimeMs > budgetMs;
819
+ const old = Date.now() - statSync2(lock).mtimeMs > budgetMs;
690
820
  if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
691
821
  rmSync2(lock, { force: true });
692
822
  continue;
@@ -700,8 +830,8 @@ async function ensureHelperCompiled(directory = tmpdir2(), budgetMs = HELPER_COM
700
830
  return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
701
831
  }
702
832
  async function compileHelper(directory, assembly, budgetMs) {
703
- const scratch = mkdtempSync(join2(directory, "gdharness-desktop-source-"));
704
- const source = join2(scratch, "helper.cs");
833
+ const scratch = mkdtempSync(join3(directory, "gdharness-desktop-source-"));
834
+ const source = join3(scratch, "helper.cs");
705
835
  writeFileSync2(source, SOURCE, "utf8");
706
836
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
707
837
  const script = [
@@ -8135,124 +8265,6 @@ var init_config_formats = __esm(() => {
8135
8265
  init_dist();
8136
8266
  });
8137
8267
 
8138
- // src/paths.ts
8139
- import { realpathSync, statSync as statSync2 } from "node:fs";
8140
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join3, relative, resolve, sep } from "node:path";
8141
- function realAsFarAsItExists(path) {
8142
- const missing = [];
8143
- let at = path;
8144
- for (;; ) {
8145
- try {
8146
- return join3(realpathSync.native(at), ...missing);
8147
- } catch {
8148
- const parent = dirname2(at);
8149
- if (parent === at) {
8150
- return path;
8151
- }
8152
- missing.unshift(basename2(at));
8153
- at = parent;
8154
- }
8155
- }
8156
- }
8157
- function isSameDirectory(onePath, otherPath) {
8158
- return relative(realAsFarAsItExists(resolve(onePath)), realAsFarAsItExists(resolve(otherPath))) === "";
8159
- }
8160
- function isWithinRoot(rootPath, candidatePath) {
8161
- const step = relative(resolve(rootPath), resolve(candidatePath));
8162
- return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
8163
- }
8164
- function keptInside(given) {
8165
- const at = given.indexOf("::");
8166
- const inner = at === -1 ? "" : given.slice(at + 2);
8167
- return KEPT_ID.test(inner) ? { file: given.slice(0, at), inner } : { file: given, inner: undefined };
8168
- }
8169
- function resolveWithinProject(projectPath, candidatePath, options = {}) {
8170
- const trimmed = candidatePath.trim();
8171
- if (trimmed === "") {
8172
- return { ok: false, reason: "Path is empty." };
8173
- }
8174
- if (trimmed.includes("\x00")) {
8175
- return { ok: false, reason: "Path contains a null byte." };
8176
- }
8177
- const root = resolve(projectPath);
8178
- const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : isAbsolute(trimmed) && isWithinRoot(root, trimmed) ? relative(root, resolve(trimmed)).split(sep).join("/") : trimmed;
8179
- if (withoutScheme.startsWith("uid://")) {
8180
- return {
8181
- ok: false,
8182
- reason: `Path '${trimmed}' is a UID, and a file is judged here by where it is on disk, which a UID does not say. Give the file's res:// path.`
8183
- };
8184
- }
8185
- const scheme = OTHER_SCHEME.exec(withoutScheme);
8186
- if (scheme) {
8187
- const elsewhere = scheme[0].toLowerCase() === "user://" ? " A game's user:// is a directory of its own outside the project: a running game's is under userDir in editor_status runtimes and in editor_run start's runtime, where a file reader of your own reads it." : "";
8188
- return {
8189
- ok: false,
8190
- reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.${elsewhere}`
8191
- };
8192
- }
8193
- if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
8194
- const tail = withoutScheme.split(/[\\/]+/).filter((step) => step !== "" && !step.endsWith(":"));
8195
- const besideIt = isAbsolute(trimmed) && isWithinRoot(realAsFarAsItExists(dirname2(resolve(trimmed))), realAsFarAsItExists(root));
8196
- const example = tail.slice(besideIt ? -1 : -2).join("/") || "scenes/main.tscn";
8197
- return {
8198
- ok: false,
8199
- reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as '${example}'.`
8200
- };
8201
- }
8202
- const kept = keptInside(trimmed);
8203
- if (kept.inner !== undefined) {
8204
- return {
8205
- ok: false,
8206
- reason: `Path '${trimmed}' names the resource ${kept.inner} kept inside ${kept.file}, not a file. resource_edit read reads one by that path; this argument takes a file.`
8207
- };
8208
- }
8209
- if (withoutScheme.includes(":")) {
8210
- return {
8211
- ok: false,
8212
- reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
8213
- };
8214
- }
8215
- const absolutePath = resolve(root, withoutScheme);
8216
- if (!isWithinRoot(root, absolutePath)) {
8217
- return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
8218
- }
8219
- const relativePath = relative(root, absolutePath).split(sep).join("/");
8220
- if (relativePath === "" && options.root !== true) {
8221
- return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
8222
- }
8223
- return { ok: true, absolutePath, relativePath };
8224
- }
8225
- function exportedBuild(projectPath, candidatePath) {
8226
- const found = resolveWithinProject(projectPath, candidatePath);
8227
- if (!found.ok) {
8228
- return found;
8229
- }
8230
- let real;
8231
- try {
8232
- real = realpathSync.native(found.absolutePath);
8233
- } catch {
8234
- return {
8235
- ok: false,
8236
- reason: `There is no ${found.relativePath} in the project to start: export the build first, with project_export run.`
8237
- };
8238
- }
8239
- if (!isWithinRoot(realAsFarAsItExists(projectPath), real)) {
8240
- return {
8241
- ok: false,
8242
- reason: `${found.relativePath} leads outside the project, to ${real}, so it is not started.`
8243
- };
8244
- }
8245
- if (!statSync2(real).isFile()) {
8246
- return { ok: false, reason: `${found.relativePath} is not a file, so there is nothing to start.` };
8247
- }
8248
- return { ok: true, absolutePath: real, relativePath: found.relativePath };
8249
- }
8250
- var RESOURCE_SCHEME = "res://", OTHER_SCHEME, KEPT_ID;
8251
- var init_paths = __esm(() => {
8252
- OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
8253
- KEPT_ID = /^[A-Za-z0-9_]+$/;
8254
- });
8255
-
8256
8268
  // src/runner.ts
8257
8269
  import { existsSync as existsSync2 } from "node:fs";
8258
8270
  import { dirname as dirname3, join as join4 } from "node:path";
@@ -9905,6 +9917,29 @@ function cachedClasses(projectPath) {
9905
9917
  }
9906
9918
  return listed;
9907
9919
  }
9920
+ function cachedEntries(projectPath) {
9921
+ const text = readWhole(join6(projectPath, ".godot", "global_script_class_cache.cfg"));
9922
+ if (text === null) {
9923
+ return null;
9924
+ }
9925
+ const listed = new Map;
9926
+ for (const entry of text.matchAll(/\{[^{}]*\}/g)) {
9927
+ const named = /"class":\s*&"([^"]+)"/.exec(entry[0])?.[1];
9928
+ if (named !== undefined) {
9929
+ listed.set(named, entry[0]);
9930
+ }
9931
+ }
9932
+ return listed;
9933
+ }
9934
+ function cacheChangesBetween(before, after) {
9935
+ if (before === null || after === null) {
9936
+ return {};
9937
+ }
9938
+ const added = [...after.keys()].filter((name) => !before.has(name)).sort();
9939
+ const removed = [...before.keys()].filter((name) => !after.has(name)).sort();
9940
+ const changed = [...after.keys()].filter((name) => before.has(name) && before.get(name) !== after.get(name)).sort();
9941
+ return { added, removed, changed };
9942
+ }
9908
9943
  function addonOf(path) {
9909
9944
  return /^res:\/\/addons\/([^/]+)\//.exec(path)?.[1];
9910
9945
  }
@@ -9921,6 +9956,20 @@ function withAddonPathsCounted(paths) {
9921
9956
  }
9922
9957
  return { own, inAddons };
9923
9958
  }
9959
+ function classesWithAddonsCounted(classes, listed) {
9960
+ const kept = [];
9961
+ const inAddons = {};
9962
+ for (const entry of classes) {
9963
+ const path = entry !== null && typeof entry === "object" ? entry["path"] : "";
9964
+ const addon = addonOf(typeof path === "string" ? path : "");
9965
+ if (addon === undefined || listed.has(addon)) {
9966
+ kept.push(entry);
9967
+ } else {
9968
+ inAddons[addon] = (inAddons[addon] ?? 0) + 1;
9969
+ }
9970
+ }
9971
+ return { classes: kept, inAddons };
9972
+ }
9924
9973
  function withAddonClassesCounted(rebuilt, listed) {
9925
9974
  const counted = { ...rebuilt };
9926
9975
  for (const key of ["added", "changed"]) {
@@ -11818,6 +11867,16 @@ var init_engine_launch = __esm(() => {
11818
11867
  MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
11819
11868
  });
11820
11869
 
11870
+ // src/cache-home.ts
11871
+ import { homedir as homedir2 } from "node:os";
11872
+ import { join as join8 } from "node:path";
11873
+ function cacheHome(env = process.env, platform = process.platform, home = homedir2()) {
11874
+ const set = (name) => env[name] === "" ? undefined : env[name];
11875
+ const local = platform === "win32" ? set("LOCALAPPDATA") : undefined;
11876
+ return join8(set("XDG_CACHE_HOME") ?? local ?? join8(home, ".cache"), "gdharness");
11877
+ }
11878
+ var init_cache_home = () => {};
11879
+
11821
11880
  // src/call-signal.ts
11822
11881
  import { AsyncLocalStorage } from "node:async_hooks";
11823
11882
  function stillGoingSaid(tool, op, elapsedMs) {
@@ -11889,8 +11948,8 @@ import {
11889
11948
  watch,
11890
11949
  writeSync as writeSync3
11891
11950
  } from "node:fs";
11892
- import { availableParallelism as availableParallelism2, homedir as homedir2 } from "node:os";
11893
- import { join as join8, resolve as resolve4 } from "node:path";
11951
+ import { availableParallelism as availableParallelism2, homedir as homedir3 } from "node:os";
11952
+ import { join as join9, resolve as resolve4 } from "node:path";
11894
11953
  import process7 from "node:process";
11895
11954
  import { setTimeout as delay3 } from "node:timers/promises";
11896
11955
  function machineSlots(asked = {
@@ -11900,15 +11959,13 @@ function machineSlots(asked = {
11900
11959
  env: process7.env,
11901
11960
  platform: process7.platform,
11902
11961
  processors: availableParallelism2(),
11903
- home: homedir2(),
11962
+ home: homedir3(),
11904
11963
  cwd: process7.cwd()
11905
11964
  }) {
11906
11965
  const { env } = machine;
11907
11966
  const named = nonEmpty(env[SLOTS_VARIABLE]);
11908
- const xdg = nonEmpty(env["XDG_CACHE_HOME"]);
11909
- const local = machine.platform === "win32" ? nonEmpty(env["LOCALAPPDATA"]) : undefined;
11910
- const cache = join8(xdg ?? local ?? join8(machine.home, ".cache"), "gdharness");
11911
- const directory = asked.directory ?? (named === undefined ? join8(cache, "slots") : resolve4(machine.cwd, named));
11967
+ const cache = cacheHome(env, machine.platform, machine.home);
11968
+ const directory = asked.directory ?? (named === undefined ? join9(cache, "slots") : resolve4(machine.cwd, named));
11912
11969
  const counted = nonEmpty(env[SLOT_COUNT_VARIABLE]);
11913
11970
  let count = asked.count;
11914
11971
  if (count === null && counted !== undefined) {
@@ -12056,7 +12113,7 @@ class EngineSlots {
12056
12113
  async takeFree() {
12057
12114
  mkdirSync3(this.directory, { recursive: true });
12058
12115
  for (let slot = 0;slot < this.count; slot += 1) {
12059
- const lock = join8(this.directory, `${String(slot)}.lock`);
12116
+ const lock = join9(this.directory, `${String(slot)}.lock`);
12060
12117
  if (this.made(lock) || await this.takenOver(lock)) {
12061
12118
  this.held.add(lock);
12062
12119
  return lock;
@@ -12065,9 +12122,9 @@ class EngineSlots {
12065
12122
  return null;
12066
12123
  }
12067
12124
  waitMarked() {
12068
- const marks = join8(this.directory, WAITING_FOLDER);
12125
+ const marks = join9(this.directory, WAITING_FOLDER);
12069
12126
  waitsMarked += 1;
12070
- const mark = join8(marks, `${String(this.holder.pid)}-${String(waitsMarked)}.json`);
12127
+ const mark = join9(marks, `${String(this.holder.pid)}-${String(waitsMarked)}.json`);
12071
12128
  try {
12072
12129
  mkdirSync3(marks, { recursive: true });
12073
12130
  } catch {
@@ -12083,7 +12140,7 @@ class EngineSlots {
12083
12140
  const found = [];
12084
12141
  for (let slot = 0;slot < this.count; slot += 1) {
12085
12142
  try {
12086
- const holder = holderOf(readFileSync8(join8(this.directory, `${String(slot)}.lock`), "utf8"));
12143
+ const holder = holderOf(readFileSync8(join9(this.directory, `${String(slot)}.lock`), "utf8"));
12087
12144
  if (holder !== null) {
12088
12145
  found.push(holder);
12089
12146
  }
@@ -12257,6 +12314,7 @@ async function inASlot(body) {
12257
12314
  var SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", waitsMarked = 0, ours, saidTo, takenBy;
12258
12315
  var init_engine_slots = __esm(() => {
12259
12316
  init_alive();
12317
+ init_cache_home();
12260
12318
  init_call_signal();
12261
12319
  init_engine_launch();
12262
12320
  init_process_children();
@@ -12265,11 +12323,11 @@ var init_engine_slots = __esm(() => {
12265
12323
 
12266
12324
  // src/detection.ts
12267
12325
  import { existsSync as existsSync6, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
12268
- import { homedir as homedir3 } from "node:os";
12269
- import { join as join9 } from "node:path";
12326
+ import { homedir as homedir4 } from "node:os";
12327
+ import { join as join10 } from "node:path";
12270
12328
  function resolveHomeDirectory() {
12271
12329
  try {
12272
- return homedir3();
12330
+ return homedir4();
12273
12331
  } catch {
12274
12332
  return "";
12275
12333
  }
@@ -12290,7 +12348,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
12290
12348
  if (!pattern.test(name)) {
12291
12349
  continue;
12292
12350
  }
12293
- const fullPath = join9(directory, name);
12351
+ const fullPath = join10(directory, name);
12294
12352
  try {
12295
12353
  const stat = statSync7(fullPath);
12296
12354
  if (stat.isFile()) {
@@ -12299,7 +12357,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
12299
12357
  } catch {}
12300
12358
  }
12301
12359
  matches.sort((a, b) => b.mtime - a.mtime);
12302
- return matches.map((m) => join9(directory, m.name));
12360
+ return matches.map((m) => join10(directory, m.name));
12303
12361
  }
12304
12362
  function conventionalPaths(platform, home) {
12305
12363
  const paths = ["godot"];
@@ -12954,26 +13012,26 @@ var init_engine_run = __esm(() => {
12954
13012
 
12955
13013
  // src/doc-cache.ts
12956
13014
  import { existsSync as existsSync8, readdirSync as readdirSync5, statSync as statSync8 } from "node:fs";
12957
- import { homedir as homedir4 } from "node:os";
12958
- import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute5, join as join10 } from "node:path";
13015
+ import { homedir as homedir5 } from "node:os";
13016
+ import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute5, join as join11 } from "node:path";
12959
13017
  import process9 from "node:process";
12960
- function editorCacheDirectory(godotPath, platform = process9.platform, env = process9.env, home = homedir4()) {
13018
+ function editorCacheDirectory(godotPath, platform = process9.platform, env = process9.env, home = homedir5()) {
12961
13019
  const beside = dirname5(godotPath);
12962
13020
  const bundle = platform === "darwin" && basename4(beside) === "MacOS" && basename4(dirname5(beside)) === "Contents" ? dirname5(dirname5(dirname5(beside))) : null;
12963
13021
  for (const folder of bundle === null ? [beside] : [beside, bundle]) {
12964
- if (existsSync8(join10(folder, "._sc_")) || existsSync8(join10(folder, "_sc_"))) {
12965
- return join10(folder, "editor_data", "cache");
13022
+ if (existsSync8(join11(folder, "._sc_")) || existsSync8(join11(folder, "_sc_"))) {
13023
+ return join11(folder, "editor_data", "cache");
12966
13024
  }
12967
13025
  }
12968
13026
  if (platform === "win32") {
12969
13027
  const set = (name) => env[name] === "" ? undefined : env[name];
12970
- return join10(set("LOCALAPPDATA") ?? set("TEMP") ?? join10(home, "AppData", "Local"), "Godot");
13028
+ return join11(set("LOCALAPPDATA") ?? set("TEMP") ?? join11(home, "AppData", "Local"), "Godot");
12971
13029
  }
12972
13030
  if (platform === "darwin") {
12973
- return join10(home, "Library", "Caches", "Godot");
13031
+ return join11(home, "Library", "Caches", "Godot");
12974
13032
  }
12975
13033
  const xdg = env["XDG_CACHE_HOME"];
12976
- return join10(xdg !== undefined && isAbsolute5(xdg) ? xdg : join10(home, ".cache"), "godot");
13034
+ return join11(xdg !== undefined && isAbsolute5(xdg) ? xdg : join11(home, ".cache"), "godot");
12977
13035
  }
12978
13036
  function docCachesWritten(directory) {
12979
13037
  const written = new Map;
@@ -12985,7 +13043,7 @@ function docCachesWritten(directory) {
12985
13043
  }
12986
13044
  for (const name of names.filter((one) => CACHE_FILE.test(one))) {
12987
13045
  try {
12988
- written.set(join10(directory, name), statSync8(join10(directory, name)).mtimeMs);
13046
+ written.set(join11(directory, name), statSync8(join11(directory, name)).mtimeMs);
12989
13047
  } catch {}
12990
13048
  }
12991
13049
  return written;
@@ -13010,7 +13068,7 @@ var init_doc_cache = __esm(() => {
13010
13068
 
13011
13069
  // src/project-scan.ts
13012
13070
  import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync9 } from "node:fs";
13013
- import { basename as basename5, extname, join as join11, relative as relative4, sep as sep3 } from "node:path";
13071
+ import { basename as basename5, extname, join as join12, relative as relative4, sep as sep3 } from "node:path";
13014
13072
  function skipped(name) {
13015
13073
  return name.startsWith(".") || SKIPPED.has(name);
13016
13074
  }
@@ -13018,10 +13076,10 @@ function steppedOver(directory) {
13018
13076
  return gdignored(directory) || installedByUs(directory);
13019
13077
  }
13020
13078
  function gdignored(directory) {
13021
- return existsSync9(join11(directory, ".gdignore"));
13079
+ return existsSync9(join12(directory, ".gdignore"));
13022
13080
  }
13023
13081
  function installedByUs(directory) {
13024
- return existsSync9(join11(directory, VERSION_MARKER));
13082
+ return existsSync9(join12(directory, VERSION_MARKER));
13025
13083
  }
13026
13084
  function projectStructure(projectPath) {
13027
13085
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -13034,7 +13092,7 @@ function projectStructure(projectPath) {
13034
13092
  continue;
13035
13093
  }
13036
13094
  if (entry.isDirectory()) {
13037
- visit(join11(directory, entry.name));
13095
+ visit(join12(directory, entry.name));
13038
13096
  } else if (entry.isFile()) {
13039
13097
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
13040
13098
  if (SIDECAR_EXTENSIONS.has(extension)) {
@@ -13064,7 +13122,7 @@ function folderFiles(directory, extensions) {
13064
13122
  function underGdignore(projectPath, relative) {
13065
13123
  const folders = relative.split("/").slice(0, -1);
13066
13124
  for (let depth = 1;depth <= folders.length; depth += 1) {
13067
- if (gdignored(join11(projectPath, ...folders.slice(0, depth)))) {
13125
+ if (gdignored(join12(projectPath, ...folders.slice(0, depth)))) {
13068
13126
  return true;
13069
13127
  }
13070
13128
  }
@@ -13081,7 +13139,7 @@ function steppedOverIn(directory) {
13081
13139
  continue;
13082
13140
  }
13083
13141
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
13084
- const path = join11(at, entry.name);
13142
+ const path = join12(at, entry.name);
13085
13143
  if (gdignored(path)) {
13086
13144
  found.push({ folder: spelled, why: "gdignore" });
13087
13145
  } else if (installedByUs(path)) {
@@ -13102,7 +13160,7 @@ function filesBelow(root, extensions) {
13102
13160
  continue;
13103
13161
  }
13104
13162
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
13105
- const path = join11(directory, entry.name);
13163
+ const path = join12(directory, entry.name);
13106
13164
  if (entry.isDirectory()) {
13107
13165
  if (!steppedOver(path)) {
13108
13166
  visit(path, spelled);
@@ -13127,14 +13185,14 @@ function scriptsWithoutUid(projectPath) {
13127
13185
  }
13128
13186
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
13129
13187
  if (entry.isDirectory()) {
13130
- visit(join11(directory, entry.name), spelled);
13188
+ visit(join12(directory, entry.name), spelled);
13131
13189
  continue;
13132
13190
  }
13133
13191
  if (!entry.isFile()) {
13134
13192
  continue;
13135
13193
  }
13136
13194
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
13137
- if (CARRIES_A_UID.has(extension) && !existsSync9(join11(directory, `${entry.name}.uid`))) {
13195
+ if (CARRIES_A_UID.has(extension) && !existsSync9(join12(directory, `${entry.name}.uid`))) {
13138
13196
  found.push(spelled);
13139
13197
  }
13140
13198
  }
@@ -13145,7 +13203,7 @@ function scriptsWithoutUid(projectPath) {
13145
13203
  function pathsInUidCache(projectPath) {
13146
13204
  let data;
13147
13205
  try {
13148
- data = readFileSync9(join11(projectPath, ".godot", "uid_cache.bin"));
13206
+ data = readFileSync9(join12(projectPath, ".godot", "uid_cache.bin"));
13149
13207
  } catch {
13150
13208
  return null;
13151
13209
  }
@@ -13173,7 +13231,7 @@ function pathsInUidCache(projectPath) {
13173
13231
  function steppedOverAround(projectPath, place) {
13174
13232
  let at = projectPath;
13175
13233
  for (const step of relative4(projectPath, place).split(sep3).filter((part) => part !== "")) {
13176
- at = join11(at, step);
13234
+ at = join12(at, step);
13177
13235
  if (!statSync9(at).isDirectory()) {
13178
13236
  break;
13179
13237
  }
@@ -13197,7 +13255,7 @@ function projectTexts(projectPath) {
13197
13255
  if (skipped(entry.name)) {
13198
13256
  continue;
13199
13257
  }
13200
- const absolute = join11(directory, entry.name);
13258
+ const absolute = join12(directory, entry.name);
13201
13259
  const spelled = `${prefix}${entry.name}`;
13202
13260
  if (entry.isDirectory()) {
13203
13261
  visit(absolute, `${spelled}/`);
@@ -13227,7 +13285,7 @@ function ignoredTexts(projectPath) {
13227
13285
  if (skipped(entry.name)) {
13228
13286
  continue;
13229
13287
  }
13230
- const absolute = join11(directory, entry.name);
13288
+ const absolute = join12(directory, entry.name);
13231
13289
  const spelled = `${prefix}${entry.name}`;
13232
13290
  if (entry.isDirectory()) {
13233
13291
  visit(absolute, `${spelled}/`, under);
@@ -13325,7 +13383,7 @@ function searchProject(projectPath, options) {
13325
13383
  if (skipped(entry.name)) {
13326
13384
  continue;
13327
13385
  }
13328
- const entryPath = join11(directory, entry.name);
13386
+ const entryPath = join12(directory, entry.name);
13329
13387
  if (entry.isDirectory()) {
13330
13388
  visit(entryPath);
13331
13389
  } else if (entry.isFile()) {
@@ -13402,7 +13460,7 @@ var init_project_scan = __esm(() => {
13402
13460
  // src/import-freshness.ts
13403
13461
  import { createHash as createHash2 } from "node:crypto";
13404
13462
  import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync10 } from "node:fs";
13405
- import { join as join12 } from "node:path";
13463
+ import { join as join13 } from "node:path";
13406
13464
  function md5Of(file) {
13407
13465
  let stats;
13408
13466
  try {
@@ -13462,7 +13520,7 @@ function importedIntoTheProject(projectPath, resPaths) {
13462
13520
  return wrote;
13463
13521
  }
13464
13522
  function onDisk(projectPath, resPath) {
13465
- return join12(projectPath, resPath.slice("res://".length));
13523
+ return join13(projectPath, resPath.slice("res://".length));
13466
13524
  }
13467
13525
  function behind(projectPath, resPath, sidecar) {
13468
13526
  const remap = sectionOf(sidecar, "remap");
@@ -13475,7 +13533,7 @@ function behind(projectPath, resPath, sidecar) {
13475
13533
  return "its imported output is not on disk";
13476
13534
  }
13477
13535
  const name = resPath.slice(resPath.lastIndexOf("/") + 1);
13478
- const record = join12(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
13536
+ const record = join13(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
13479
13537
  let recorded = "";
13480
13538
  try {
13481
13539
  recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync10(record, "utf8"))?.[1] ?? "";
@@ -13491,7 +13549,7 @@ function behind(projectPath, resPath, sidecar) {
13491
13549
  function staleImports(projectPath) {
13492
13550
  const stale = [];
13493
13551
  const visit = (directory, resDirectory) => {
13494
- if (existsSync10(join12(directory, ".gdignore")) || existsSync10(join12(directory, VERSION_MARKER))) {
13552
+ if (existsSync10(join13(directory, ".gdignore")) || existsSync10(join13(directory, VERSION_MARKER))) {
13495
13553
  return;
13496
13554
  }
13497
13555
  let entries;
@@ -13507,7 +13565,7 @@ function staleImports(projectPath) {
13507
13565
  }
13508
13566
  const resPath = `${resDirectory}${entry.name}`;
13509
13567
  if (entry.isDirectory()) {
13510
- visit(join12(directory, entry.name), `${resPath}/`);
13568
+ visit(join13(directory, entry.name), `${resPath}/`);
13511
13569
  continue;
13512
13570
  }
13513
13571
  if (!entry.isFile()) {
@@ -13520,7 +13578,7 @@ function staleImports(projectPath) {
13520
13578
  }
13521
13579
  let sidecar;
13522
13580
  try {
13523
- sidecar = readFileSync10(join12(directory, entry.name), "utf8");
13581
+ sidecar = readFileSync10(join13(directory, entry.name), "utf8");
13524
13582
  } catch {
13525
13583
  continue;
13526
13584
  }
@@ -13589,7 +13647,7 @@ import {
13589
13647
  unlinkSync,
13590
13648
  writeFileSync as writeFileSync6
13591
13649
  } from "node:fs";
13592
- import { basename as basename6, dirname as dirname6, isAbsolute as isAbsolute6, join as join13 } from "node:path";
13650
+ import { basename as basename6, dirname as dirname6, isAbsolute as isAbsolute6, join as join14 } from "node:path";
13593
13651
  function freeLibraryCopies(projectPath, platform = process.platform) {
13594
13652
  const setAside = [];
13595
13653
  const swept = [];
@@ -13599,10 +13657,10 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
13599
13657
  for (const library of windowsLibraries(projectPath)) {
13600
13658
  const folder = dirname6(library);
13601
13659
  const copy = `~${basename6(library)}`;
13602
- if (existsSync11(join13(folder, copy))) {
13603
- const aside = join13(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
13660
+ if (existsSync11(join14(folder, copy))) {
13661
+ const aside = join14(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
13604
13662
  try {
13605
- renameSync2(join13(folder, copy), aside);
13663
+ renameSync2(join14(folder, copy), aside);
13606
13664
  setAside.push(aside);
13607
13665
  } catch {}
13608
13666
  }
@@ -13615,8 +13673,8 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
13615
13673
  for (const name of names) {
13616
13674
  if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
13617
13675
  try {
13618
- unlinkSync(join13(folder, name));
13619
- swept.push(join13(folder, name));
13676
+ unlinkSync(join14(folder, name));
13677
+ swept.push(join14(folder, name));
13620
13678
  } catch {}
13621
13679
  }
13622
13680
  }
@@ -13624,13 +13682,13 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
13624
13682
  return { setAside, swept };
13625
13683
  }
13626
13684
  function seedExtensionList(projectPath) {
13627
- const list = join13(projectPath, ".godot", "extension_list.cfg");
13685
+ const list = join14(projectPath, ".godot", "extension_list.cfg");
13628
13686
  if (existsSync11(list)) {
13629
13687
  return null;
13630
13688
  }
13631
13689
  const found = [];
13632
13690
  const visit = (directory, resDirectory) => {
13633
- if (existsSync11(join13(directory, ".gdignore"))) {
13691
+ if (existsSync11(join14(directory, ".gdignore"))) {
13634
13692
  return;
13635
13693
  }
13636
13694
  let entries;
@@ -13644,7 +13702,7 @@ function seedExtensionList(projectPath) {
13644
13702
  continue;
13645
13703
  }
13646
13704
  if (entry.isDirectory()) {
13647
- visit(join13(directory, entry.name), `${resDirectory}${entry.name}/`);
13705
+ visit(join14(directory, entry.name), `${resDirectory}${entry.name}/`);
13648
13706
  } else if (entry.name.endsWith(".gdextension")) {
13649
13707
  found.push(`${resDirectory}${entry.name}`);
13650
13708
  }
@@ -13662,7 +13720,7 @@ function seedExtensionList(projectPath) {
13662
13720
  return found;
13663
13721
  }
13664
13722
  function windowsLibraries(projectPath) {
13665
- const listed = readOrNull(join13(projectPath, ".godot", "extension_list.cfg"));
13723
+ const listed = readOrNull(join14(projectPath, ".godot", "extension_list.cfg"));
13666
13724
  if (listed === null) {
13667
13725
  return [];
13668
13726
  }
@@ -13694,9 +13752,9 @@ function resolveResource(projectPath, relativeTo, path) {
13694
13752
  return null;
13695
13753
  }
13696
13754
  if (path.startsWith("res://")) {
13697
- return join13(projectPath, path.slice("res://".length));
13755
+ return join14(projectPath, path.slice("res://".length));
13698
13756
  }
13699
- return isAbsolute6(path) ? path : join13(relativeTo, path);
13757
+ return isAbsolute6(path) ? path : join14(relativeTo, path);
13700
13758
  }
13701
13759
  function readOrNull(path) {
13702
13760
  try {
@@ -13710,10 +13768,10 @@ var init_library_copies = () => {};
13710
13768
  // src/reimport.ts
13711
13769
  import { createHash as createHash3 } from "node:crypto";
13712
13770
  import { existsSync as existsSync12, readFileSync as readFileSync12, rmSync as rmSync7, statSync as statSync11, utimesSync, writeFileSync as writeFileSync7 } from "node:fs";
13713
- import { basename as basename7, join as join14 } from "node:path";
13771
+ import { basename as basename7, join as join15 } from "node:path";
13714
13772
  function forceNextImport(projectPath, resourcePath) {
13715
13773
  const relative = resourcePath.replace(/^res:\/\//, "");
13716
- const sidecar = join14(projectPath, `${relative}.import`);
13774
+ const sidecar = join15(projectPath, `${relative}.import`);
13717
13775
  if (!existsSync12(sidecar)) {
13718
13776
  return;
13719
13777
  }
@@ -13724,17 +13782,17 @@ function forceNextImport(projectPath, resourcePath) {
13724
13782
  }
13725
13783
  function importRecord(projectPath, resourcePath) {
13726
13784
  const digest = createHash3("md5").update(resourcePath).digest("hex");
13727
- return join14(projectPath, ".godot", "imported", `${basename7(resourcePath)}-${digest}.md5`);
13785
+ return join15(projectPath, ".godot", "imported", `${basename7(resourcePath)}-${digest}.md5`);
13728
13786
  }
13729
13787
  var init_reimport = () => {};
13730
13788
 
13731
13789
  // src/scratch.ts
13732
13790
  import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readdirSync as readdirSync9, rmSync as rmSync8 } from "node:fs";
13733
13791
  import { tmpdir as tmpdir3 } from "node:os";
13734
- import { join as join15 } from "node:path";
13792
+ import { join as join16 } from "node:path";
13735
13793
  import process10 from "node:process";
13736
13794
  function scratchDirectory(kind) {
13737
- return mkdtempSync2(join15(tmpdir3(), `gdharness-${kind}-${process10.pid}-`));
13795
+ return mkdtempSync2(join16(tmpdir3(), `gdharness-${kind}-${process10.pid}-`));
13738
13796
  }
13739
13797
  function sweepAbandonedScratch(directory = tmpdir3()) {
13740
13798
  let entries;
@@ -13750,7 +13808,7 @@ function sweepAbandonedScratch(directory = tmpdir3()) {
13750
13808
  continue;
13751
13809
  }
13752
13810
  try {
13753
- remove(join15(directory, entry));
13811
+ remove(join16(directory, entry));
13754
13812
  } catch {}
13755
13813
  }
13756
13814
  }
@@ -13836,7 +13894,7 @@ var init_scratch = __esm(() => {
13836
13894
  });
13837
13895
 
13838
13896
  // src/script-origins.ts
13839
- import { join as join16 } from "node:path";
13897
+ import { join as join17 } from "node:path";
13840
13898
  function unresolvedClassIn(message) {
13841
13899
  return UNRESOLVED.exec(message)?.[1] ?? null;
13842
13900
  }
@@ -13898,7 +13956,7 @@ function checkWithEngine(godotPath, projectPath) {
13898
13956
  const ran = await runEngine(godotPath, [
13899
13957
  "--headless",
13900
13958
  "--log-file",
13901
- join16(logDir, "engine.log"),
13959
+ join17(logDir, "engine.log"),
13902
13960
  "--path",
13903
13961
  projectPath,
13904
13962
  "--check-only",
@@ -13945,7 +14003,7 @@ var init_script_origins = __esm(() => {
13945
14003
 
13946
14004
  // src/headless.ts
13947
14005
  import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
13948
- import { dirname as dirname7, join as join17 } from "node:path";
14006
+ import { dirname as dirname7, join as join18 } from "node:path";
13949
14007
  function snakeCased(params) {
13950
14008
  const result = emptyRecord();
13951
14009
  for (const [key, value] of Object.entries(params)) {
@@ -14101,7 +14159,7 @@ async function importOnce(godotPath, projectPath) {
14101
14159
  ran = await runEngine(godotPath, [
14102
14160
  "--headless",
14103
14161
  "--log-file",
14104
- join17(logDir, "engine.log"),
14162
+ join18(logDir, "engine.log"),
14105
14163
  "--path",
14106
14164
  projectPath,
14107
14165
  "--import"
@@ -14137,14 +14195,14 @@ async function runOperation(engine, operation, params, projectPath) {
14137
14195
  }
14138
14196
  async function operationOutcome(engine, operation, params, projectPath) {
14139
14197
  const paramsDir = scratchDirectory("params");
14140
- const paramsFile = join17(paramsDir, `${operation}.json`);
14141
- const answerFile = join17(paramsDir, "answer.json");
14198
+ const paramsFile = join18(paramsDir, `${operation}.json`);
14199
+ const answerFile = join18(paramsDir, "answer.json");
14142
14200
  writeFileSync8(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
14143
14201
  const args = [
14144
14202
  "--headless",
14145
14203
  ...engine.editor === true ? ["--editor"] : [],
14146
14204
  "--log-file",
14147
- join17(paramsDir, "engine.log"),
14205
+ join18(paramsDir, "engine.log"),
14148
14206
  "--path",
14149
14207
  projectPath,
14150
14208
  "--script",
@@ -14222,7 +14280,8 @@ async function operationOutcome(engine, operation, params, projectPath) {
14222
14280
  ...afterAnswered
14223
14281
  };
14224
14282
  }
14225
- const afterAnswer = ran.exitCode === 0 && ran.failure === null ? undefined : `The operation finished and wrote this answer, and then the engine did not exit cleanly (${howItEnded(ran)}). Anything it reported on the way out is under ${answered === -1 ? "engine_messages" : "engine_messages_after_answer"}.`;
14283
+ const onTheWayOut = answered === -1 ? problems : after;
14284
+ const afterAnswer = ran.exitCode === 0 && ran.failure === null ? undefined : `The operation finished and wrote this answer, and then the engine did not exit cleanly (${howItEnded(ran)}). ${onTheWayOut.length === 0 ? "It printed no error or warning on the way out." : `What it reported on the way out is under ${answered === -1 ? "engine_messages" : "engine_messages_after_answer"}.`}`;
14226
14285
  return {
14227
14286
  ok: true,
14228
14287
  payload: answer.payload,
@@ -14261,8 +14320,8 @@ var init_headless = __esm(() => {
14261
14320
 
14262
14321
  // src/update-check.ts
14263
14322
  import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
14264
- import { homedir as homedir5, tmpdir as tmpdir4 } from "node:os";
14265
- import { join as join18 } from "node:path";
14323
+ import { homedir as homedir6, tmpdir as tmpdir4 } from "node:os";
14324
+ import { join as join19 } from "node:path";
14266
14325
  function packageUrl(root) {
14267
14326
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
14268
14327
  }
@@ -14282,22 +14341,22 @@ function cacheDirectory(environment) {
14282
14341
  const value = environment[name];
14283
14342
  return value !== undefined && value !== "" ? value : null;
14284
14343
  };
14285
- const home = set("HOME") ?? homedir5();
14344
+ const home = set("HOME") ?? homedir6();
14286
14345
  if (process.platform === "win32") {
14287
- return join18(set("LOCALAPPDATA") ?? home, "gdharness");
14346
+ return join19(set("LOCALAPPDATA") ?? home, "gdharness");
14288
14347
  }
14289
14348
  if (process.platform === "darwin") {
14290
- return join18(home, "Library", "Caches", "gdharness");
14349
+ return join19(home, "Library", "Caches", "gdharness");
14291
14350
  }
14292
- return join18(set("XDG_CACHE_HOME") ?? join18(home, ".cache"), "gdharness");
14351
+ return join19(set("XDG_CACHE_HOME") ?? join19(home, ".cache"), "gdharness");
14293
14352
  }
14294
14353
  function cacheFile(environment = process.env) {
14295
14354
  try {
14296
14355
  const directory = cacheDirectory(environment);
14297
14356
  mkdirSync5(directory, { recursive: true, mode: 448 });
14298
- return join18(directory, "update-check.json");
14357
+ return join19(directory, "update-check.json");
14299
14358
  } catch {
14300
- return join18(tmpdir4(), "gdharness-update-check.json");
14359
+ return join19(tmpdir4(), "gdharness-update-check.json");
14301
14360
  }
14302
14361
  }
14303
14362
  function readCache(path) {
@@ -14785,7 +14844,7 @@ var init_ports = __esm(() => {
14785
14844
  import { existsSync as existsSync16, realpathSync as realpathSync2 } from "node:fs";
14786
14845
  import { readFile, realpath } from "node:fs/promises";
14787
14846
  import { createConnection } from "node:net";
14788
- import { dirname as dirname8, join as join19, resolve as resolve6 } from "node:path";
14847
+ import { dirname as dirname8, extname as extname2, join as join20, resolve as resolve6 } from "node:path";
14789
14848
  import { setTimeout as delay4 } from "node:timers/promises";
14790
14849
  import { fileURLToPath, pathToFileURL } from "node:url";
14791
14850
  function initializeTimeoutMs() {
@@ -15463,7 +15522,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
15463
15522
  } catch {
15464
15523
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
15465
15524
  }
15466
- if (!existsSync16(join19(projectPath, "project.godot"))) {
15525
+ if (!existsSync16(join20(projectPath, "project.godot"))) {
15467
15526
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
15468
15527
  }
15469
15528
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -15476,6 +15535,11 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
15476
15535
  } catch {
15477
15536
  throw new ArgumentRefusal(`Script file does not exist: res://${contained.relativePath}`);
15478
15537
  }
15538
+ if (!scriptPath.toLowerCase().endsWith(".gd")) {
15539
+ const said = `res://${contained.relativePath} is not a GDScript file, and the language server reads .gd files only`;
15540
+ const kind = extname2(scriptPath).toLowerCase();
15541
+ throw new ArgumentRefusal(kind === ".tscn" || kind === ".scn" ? `${said}: scene_tree reads a scene's nodes, and with properties the script each carries.` : kind === ".tres" || kind === ".res" ? `${said}: resource_edit read reads a resource.` : `${said}.`);
15542
+ }
15479
15543
  if (!isWithinRoot(projectPath, scriptPath)) {
15480
15544
  throw new ArgumentRefusal("scriptPath resolves outside the project root boundary.");
15481
15545
  }
@@ -15624,7 +15688,7 @@ var init_lsp_client = __esm(() => {
15624
15688
 
15625
15689
  // src/resources.ts
15626
15690
  import { readFileSync as readFileSync17, realpathSync as realpathSync3 } from "node:fs";
15627
- import { extname as extname2, resolve as resolve8 } from "node:path";
15691
+ import { extname as extname3, resolve as resolve8 } from "node:path";
15628
15692
  function ensureProjectPath(getProjectPath) {
15629
15693
  const projectPath = getProjectPath();
15630
15694
  if (!projectPath) {
@@ -15683,7 +15747,7 @@ function parseGodotUri(uri) {
15683
15747
  }
15684
15748
  function ensureAllowedExtension(kind, filePath) {
15685
15749
  const allowed = EXTENSIONS[kind];
15686
- if (!allowed.includes(extname2(filePath).toLowerCase())) {
15750
+ if (!allowed.includes(extname3(filePath).toLowerCase())) {
15687
15751
  throw new Refusal(`godot://${kind}/ reads only ${allowed.join(", ")} files.`);
15688
15752
  }
15689
15753
  }
@@ -15981,7 +16045,7 @@ import {
15981
16045
  statSync as statSync12,
15982
16046
  writeFileSync as writeFileSync10
15983
16047
  } from "node:fs";
15984
- import { dirname as dirname9, join as join21 } from "node:path";
16048
+ import { dirname as dirname9, join as join22 } from "node:path";
15985
16049
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15986
16050
  function addonMarking(name) {
15987
16051
  if (name === RUNTIME_ADDON) {
@@ -16002,7 +16066,7 @@ function addonsDigest(names, from) {
16002
16066
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
16003
16067
  continue;
16004
16068
  }
16005
- const path = join21(directory, name);
16069
+ const path = join22(directory, name);
16006
16070
  const inside = `${relative}/${name}`;
16007
16071
  if (statSync12(path).isDirectory()) {
16008
16072
  walk(path, inside);
@@ -16016,7 +16080,7 @@ function addonsDigest(names, from) {
16016
16080
  }
16017
16081
  };
16018
16082
  for (const name of names) {
16019
- walk(join21(from, name), name);
16083
+ walk(join22(from, name), name);
16020
16084
  }
16021
16085
  return hash.digest("hex");
16022
16086
  }
@@ -16031,11 +16095,30 @@ function shippedRuntimeDigest() {
16031
16095
  function installedEditorDigest(projectPath) {
16032
16096
  return digestMarker(projectPath, ADDONS[0]);
16033
16097
  }
16098
+ function editorUpgradeNote(sameCode, editors) {
16099
+ if (sameCode) {
16100
+ return ` 1. Nothing for the editor: this upgrade left the editor addons the same code as before,
16101
+ ` + ` so it gives an open editor nothing to restart for. editor_status says addonIsStale
16102
+ ` + " if the editor is running other code.";
16103
+ }
16104
+ if (editors === null) {
16105
+ return ` 1. An editor of this project that is open is still running the addons it loaded at
16106
+ ` + ` startup. Restart it with the editor_launch restart tool, which closes and reopens
16107
+ ` + " the window.";
16108
+ }
16109
+ if (editors.length === 0) {
16110
+ return ` 1. Nothing for the editor: none of this project is running, and the next one opened loads
16111
+ these addons.`;
16112
+ }
16113
+ const which = editors.length === 1 ? `The open editor (pid ${editors[0]}) is` : `The open editors (pids ${editors.join(", ")}) are`;
16114
+ return ` 1. ${which} still running the addons loaded at startup. Restart with the
16115
+ ` + " editor_launch restart tool, which closes and reopens the window.";
16116
+ }
16034
16117
  function installedRuntimeDigest(projectPath) {
16035
16118
  return digestMarker(projectPath, RUNTIME_ADDON);
16036
16119
  }
16037
16120
  function digestMarker(projectPath, addon) {
16038
- const marker = join21(projectPath, "addons", addon, DIGEST_MARKER);
16121
+ const marker = join22(projectPath, "addons", addon, DIGEST_MARKER);
16039
16122
  if (!existsSync18(marker)) {
16040
16123
  return null;
16041
16124
  }
@@ -16043,16 +16126,16 @@ function digestMarker(projectPath, addon) {
16043
16126
  return said === "" ? null : said;
16044
16127
  }
16045
16128
  function shippedAddonsDirectory() {
16046
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "addons");
16129
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "addons");
16047
16130
  }
16048
16131
  function shippedOperationsScript() {
16049
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "operations", "godot_operations.gd");
16132
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "operations", "godot_operations.gd");
16050
16133
  }
16051
16134
  function shippedLoader() {
16052
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "loader", "loader.gd");
16135
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "loader", "loader.gd");
16053
16136
  }
16054
16137
  function loaderState(projectPath) {
16055
- const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16138
+ const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16056
16139
  if (!existsSync18(copy)) {
16057
16140
  return "missing";
16058
16141
  }
@@ -16065,13 +16148,13 @@ function writeLoader(projectPath) {
16065
16148
  if (loaderState(projectPath) === "current") {
16066
16149
  return false;
16067
16150
  }
16068
- const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16151
+ const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16069
16152
  mkdirSync6(dirname9(copy), { recursive: true });
16070
16153
  writeFileSync10(copy, readFileSync18(shippedLoader()));
16071
16154
  return true;
16072
16155
  }
16073
16156
  function removeLoader(projectPath) {
16074
- const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16157
+ const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16075
16158
  if (!existsSync18(copy)) {
16076
16159
  return false;
16077
16160
  }
@@ -16084,14 +16167,14 @@ function removeLoader(projectPath) {
16084
16167
  return true;
16085
16168
  }
16086
16169
  function shippedListedRunner() {
16087
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "tests", "listed.gd");
16170
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "tests", "listed.gd");
16088
16171
  }
16089
16172
  function gdUnit4Unusable(projectPath) {
16090
16173
  const marker = "addons/gdUnit4/plugin.cfg";
16091
- if (!existsSync18(join21(projectPath, ...marker.split("/")))) {
16174
+ if (!existsSync18(join22(projectPath, ...marker.split("/")))) {
16092
16175
  return { missing: true, reason: `gdUnit4 is not installed in this project: no ${marker}` };
16093
16176
  }
16094
- if (!existsSync18(join21(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
16177
+ if (!existsSync18(join22(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
16095
16178
  return {
16096
16179
  missing: false,
16097
16180
  reason: `gdUnit4 in this project is older than 6.0, which the shipped runner needs: no ${GDUNIT4_CI_RUNNER}`
@@ -16102,27 +16185,27 @@ function gdUnit4Unusable(projectPath) {
16102
16185
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
16103
16186
  const installed = [];
16104
16187
  for (const name of ADDONS) {
16105
- const source = join21(from, name);
16106
- if (!existsSync18(join21(source, addonMarking(name)))) {
16188
+ const source = join22(from, name);
16189
+ if (!existsSync18(join22(source, addonMarking(name)))) {
16107
16190
  throw new Error(`The package holds no ${name} addon at ${source}.`);
16108
16191
  }
16109
- const target = join21(projectPath, "addons", name);
16192
+ const target = join22(projectPath, "addons", name);
16110
16193
  const replaced = existsSync18(target);
16111
16194
  rmSync9(target, { recursive: true, force: true });
16112
16195
  mkdirSync6(dirname9(target), { recursive: true });
16113
16196
  cpSync(source, target, { recursive: true });
16114
- writeFileSync10(join21(target, VERSION_MARKER), `${SERVER_VERSION}
16197
+ writeFileSync10(join22(target, VERSION_MARKER), `${SERVER_VERSION}
16115
16198
  `);
16116
16199
  installed.push({ name, path: target, replaced });
16117
16200
  }
16118
- writeFileSync10(join21(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
16201
+ writeFileSync10(join22(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
16119
16202
  `);
16120
- writeFileSync10(join21(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
16203
+ writeFileSync10(join22(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
16121
16204
  `);
16122
16205
  return installed;
16123
16206
  }
16124
16207
  function installedAddonVersion(projectPath) {
16125
- const marker = join21(projectPath, "addons", ADDONS[0], VERSION_MARKER);
16208
+ const marker = join22(projectPath, "addons", ADDONS[0], VERSION_MARKER);
16126
16209
  if (!existsSync18(marker)) {
16127
16210
  return null;
16128
16211
  }
@@ -16132,13 +16215,13 @@ function installedAddonVersion(projectPath) {
16132
16215
  function removeAddons(projectPath) {
16133
16216
  const removed = [];
16134
16217
  for (const name of ADDONS) {
16135
- const target = join21(projectPath, "addons", name);
16218
+ const target = join22(projectPath, "addons", name);
16136
16219
  if (existsSync18(target)) {
16137
16220
  rmSync9(target, { recursive: true, force: true });
16138
16221
  removed.push(target);
16139
16222
  }
16140
16223
  }
16141
- const addons = join21(projectPath, "addons");
16224
+ const addons = join22(projectPath, "addons");
16142
16225
  if (removed.length > 0 && existsSync18(addons) && readdirSync11(addons).length === 0) {
16143
16226
  rmdirSync(addons);
16144
16227
  removed.push(addons);
@@ -16181,7 +16264,7 @@ function loaderAmong(projectPath, named) {
16181
16264
  }
16182
16265
  let source = null;
16183
16266
  try {
16184
- source = readFileSync18(join21(projectPath, path), "utf8");
16267
+ source = readFileSync18(join22(projectPath, path), "utf8");
16185
16268
  } catch {}
16186
16269
  if (source?.includes(RUNTIME_SCRIPT)) {
16187
16270
  return { name: entry, path, exists: true };
@@ -16237,8 +16320,8 @@ function enabledPlugins(settings) {
16237
16320
  function inspectProject(projectPath) {
16238
16321
  const problems = [];
16239
16322
  const addons = ADDONS.map((name) => {
16240
- const directory = join21(projectPath, "addons", name);
16241
- const marker = join21(directory, VERSION_MARKER);
16323
+ const directory = join22(projectPath, "addons", name);
16324
+ const marker = join22(directory, VERSION_MARKER);
16242
16325
  const installed = existsSync18(directory);
16243
16326
  const version = existsSync18(marker) ? readFileSync18(marker, "utf8").trim() : null;
16244
16327
  const current = version === SERVER_VERSION;
@@ -16249,7 +16332,7 @@ function inspectProject(projectPath) {
16249
16332
  }
16250
16333
  return { name, installed, version, current };
16251
16334
  });
16252
- const settings = parseProjectGodot(readFileSync18(join21(projectPath, "project.godot"), "utf8"));
16335
+ const settings = parseProjectGodot(readFileSync18(join22(projectPath, "project.godot"), "utf8"));
16253
16336
  const pluginsEnabled = enabledPlugins(settings);
16254
16337
  for (const name of EDITOR_PLUGINS) {
16255
16338
  if (!pluginsEnabled.includes(name)) {
@@ -16273,7 +16356,7 @@ function inspectProject(projectPath) {
16273
16356
  const ignoredAutoloads = [];
16274
16357
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
16275
16358
  for (const [name, path] of named) {
16276
- if (!existsSync18(join21(projectPath, path))) {
16359
+ if (!existsSync18(join22(projectPath, path))) {
16277
16360
  if (!addonMissing(path)) {
16278
16361
  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`);
16279
16362
  }
@@ -16502,6 +16585,11 @@ var init_tool_definitions = __esm(() => {
16502
16585
  },
16503
16586
  nodePath: { type: "string", description: "Target node, relative to the AnimationPlayer root." },
16504
16587
  property: { type: "string", description: "For property tracks: the property to animate." },
16588
+ update: {
16589
+ type: "string",
16590
+ enum: ["continuous", "discrete", "capture"],
16591
+ description: "For property tracks: how the value moves between keys. continuous blends toward the next key, so a bool or an int changes halfway to it; discrete holds each key's value until the next; capture blends from the value the property holds when playing starts. Without it a new track is made as the editor makes one: as the RESET animation's track for the same property, otherwise continuous for a float, vector, colour or transform and discrete for anything else. The answer gives the track's update."
16592
+ },
16505
16593
  method: {
16506
16594
  type: "string",
16507
16595
  description: "For method tracks: the method to call. A method track holds every method key for its node, so a key calling another method joins the node's track, with existingTrack true. The answer's methodFound says whether the target node has it; a method it lacks is still added, while a nodePath that reaches no node is refused, since it may be written next, and until it exists each call the track makes fails. A key whose args the method cannot take (too few or too many, or an object where it takes a value or the reverse) is written too, and answers argumentsFit false with misfit saying why. remove_track: only the keys calling this method."
@@ -16642,7 +16730,12 @@ var init_tool_definitions = __esm(() => {
16642
16730
  include: {
16643
16731
  type: "array",
16644
16732
  items: { type: "string", enum: [...PROJECT_INFO_SECTIONS] },
16645
- description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, each input action a script reads by a name written in the call that neither the project nor the engine defines, with every line reading it, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
16733
+ description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files (those an addon declares counted per addon under inAddons, unless addonClasses names the addon), the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets (runnable marks the one preset of its platform the editor's one-click deploy uses), the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, each input action a script reads by a name written in the call that neither the project nor the engine defines, with every line reading it, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
16734
+ },
16735
+ addonClasses: {
16736
+ type: "array",
16737
+ items: { type: "string" },
16738
+ description: "For classes: the addons, by their folder under res://addons, whose classes are listed by name and script like the project's own, rather than counted under inAddons. Default none: a game with two large addons answered 686 classes, 19 of them its own."
16646
16739
  },
16647
16740
  preset: { type: "string", description: "For validation: the export preset to validate against." },
16648
16741
  detail: {
@@ -16673,7 +16766,7 @@ var init_tool_definitions = __esm(() => {
16673
16766
  prefix: {
16674
16767
  type: "string",
16675
16768
  ops: ["get"],
16676
- description: 'get: answer with every setting whose name starts with this, and the type the engine registers for each, instead of one setting by name. "debug/gdscript/warnings/" answers the whole family. The type matters: a family of levels can hold a setting that is a bool, and writing a level over it looks like it worked.'
16769
+ description: 'get: answer with every setting whose name starts with this, and the type the engine registers for each, a typed array or dictionary with what it holds as project.godot writes it (Dictionary[String, bool]), instead of one setting by name. "debug/gdscript/warnings/" answers the whole family. The type matters: a family of levels can hold a setting that is a bool, and writing a level over it looks like it worked.'
16677
16770
  },
16678
16771
  value: {
16679
16772
  blank: true,
@@ -16854,12 +16947,12 @@ var init_tool_definitions = __esm(() => {
16854
16947
  direction: {
16855
16948
  type: "string",
16856
16949
  enum: ["forward", "reverse"],
16857
- description: "forward: what this resource loads, with each reference closing a cycle marked circular and the cycle under circular_references, a script's dependencies including every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
16950
+ description: "forward: what this resource loads, with each reference closing a cycle marked circular and the cycle under circular_references, a script's dependencies including every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. A file's dependencies are listed at its first entry, and again further down only where the depth cut that list short and more is left; every other entry for a file naming any carries dependencies_listed_above. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
16858
16951
  },
16859
16952
  depth: {
16860
16953
  type: "integer",
16861
16954
  minimum: 1,
16862
- description: "forward: how many levels to follow, 1 being what the file loads itself. Default unlimited."
16955
+ description: "forward: how many levels to follow, 1 being what the file loads itself. Default unlimited, which follows every class_name use too and in a project whose scripts name each other's classes reaches most of it: a game of a hundred scripts with large addons answered 488 files in about a megabyte from its main scene. A depth of 2 or 3 keeps the answer to what is near."
16863
16956
  },
16864
16957
  includeBuiltin: {
16865
16958
  type: "boolean",
@@ -17135,7 +17228,7 @@ var init_tool_definitions = __esm(() => {
17135
17228
  name: "scene_node",
17136
17229
  title: "Scene nodes",
17137
17230
  description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, paint TileMap cells, or bake a navigation region. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own, and answers each rewrite under retargeted and each path it left under notRetargeted. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A delete rewrites no path, and answers under stillNaming what named the deleted node and now names nothing. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
17138
- answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}; namesNoNode, each node path written naming no node; sharedWith, for a property written through a resource built into the scene, each other {node, property} holding it, which changed too; setByScript, what a script that is not a tool set as a headless write ran, which the file does not hold; loadsAs, a value given that the file holds and the node's own script changes as the scene loads, as it loads. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
17231
+ answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node; a property typed as a node, or an array or dictionary of nodes, holds the node itself, so the one that held a deleted node holds null there, as the editor saves it, and its entry adds cleared: true. deletedWithIt lists each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn or that the new parent sets, as a container gives a Control its layout_mode, as {was, now}; namesNoNode, each node path written naming no node; sharedWith, for a property written through a resource built into the scene, each other {node, property} holding it, which changed too; setByScript, what a script that is not a tool set as a headless write ran, which the file does not hold; loadsAs, a value given that the file holds and the node's own script changes as the scene loads, as it loads. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
17139
17232
  parameters: {
17140
17233
  projectPath: PROJECT_PATH,
17141
17234
  scenePath: SCENE_PATH,
@@ -17177,7 +17270,11 @@ var init_tool_definitions = __esm(() => {
17177
17270
  ops: ["set"],
17178
17271
  description: "set: groups to take the node out of. One it is not in is refused, and so is one another scene puts it in (the scene that makes it, instances it or is inherited here), which only that scene can take it out of."
17179
17272
  },
17180
- newName: { type: "string", ops: ["duplicate"], description: "duplicate: the copy's name." },
17273
+ newName: {
17274
+ type: "string",
17275
+ ops: ["duplicate"],
17276
+ description: "duplicate: the copy's name. The copy has the nodes under the original and their connections, as the editor's Duplicate makes it: each connection keeps its flags, binds and unbinds, and one between two copied nodes joins the copies."
17277
+ },
17181
17278
  newParentPath: {
17182
17279
  type: "string",
17183
17280
  ops: ["reparent"],
@@ -17249,7 +17346,7 @@ var init_tool_definitions = __esm(() => {
17249
17346
  {
17250
17347
  name: "scene_signal",
17251
17348
  title: "Scene signals",
17252
- description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connection carries binds, the values passed after the signal's own, and unbinds, how many of the signal's own are dropped first, as the editor's connect dialog offers them; each is answered only when there is one. A connect of a pair already connected says alreadyConnected, with flagsBefore, bindsBefore and unbindsBefore when it changed them, and keeps the binds and unbinds it had unless the call gives new ones. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. One whose method cannot take what the signal passes, with its binds and unbinds (too few or too many arguments, an object where the method takes a value or the reverse, or an engine class unrelated to the one it takes), is written too, and answers argumentsFit false with misfit saying why; list gives each connection its methodFound, and misfit when it has one. list marks editable false on a connection a scene this one instances or inherits makes, on its own root as well as inside it, and names that scene under madeIn; only that scene can change it, so a disconnect of one is refused. ${WITHOUT_AN_EDITOR} ${ALSO_SAVED}`,
17349
+ description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connection carries binds, the values passed after the signal's own, and unbinds, how many of the signal's own are dropped first, as the editor's connect dialog offers them; each is answered only when there is one. A connect of a pair already connected says alreadyConnected, with flagsBefore, bindsBefore and unbindsBefore when it changed them, and keeps the binds and unbinds it had unless the call gives new ones. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. That holds only without binds and unbinds: the engine makes a connection that binds or unbinds arguments only to a method its target has, and a scene loads without one aimed elsewhere, so such a connect is refused. A file can hold one already, written by hand or before its method was renamed: the engine loads the scene without it and a save drops it, so every tool that writes a .tscn puts it back as the file held it and says so under unmadeConnections, a write to a binary .scn holding one is refused, and a disconnect takes it out. One whose method cannot take what the signal passes, with its binds and unbinds (too few or too many arguments, an object where the method takes a value or the reverse, or an engine class unrelated to the one it takes), is written too, and answers argumentsFit false with misfit saying why; list gives each connection its methodFound, and misfit when it has one. list marks editable false on a connection a scene this one instances or inherits makes, on its own root as well as inside it, and names that scene under madeIn; only that scene can change it, so a disconnect of one is refused. ${WITHOUT_AN_EDITOR} ${ALSO_SAVED}`,
17253
17350
  parameters: {
17254
17351
  projectPath: PROJECT_PATH,
17255
17352
  scenePath: SCENE_PATH,
@@ -17545,7 +17642,7 @@ var init_tool_definitions = __esm(() => {
17545
17642
  operations: {
17546
17643
  structure: {
17547
17644
  effect: "read",
17548
- summary: `functions (each parameter's default the source text after its =, so = null reads "null", and null for a parameter with none), variables (static ones marked is_static; one written with := has the type of a literal or built-in constructor it is given, marked inferred, as a constant always does), signals, constants, enums, inner_classes (each with its name, line and extends, and its own members in these same fields), class_name, extends and line_count, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies. A member or inner class with a ## documentation comment directly above it carries the comment's text as doc, and the script's own, the ## block above class_name or extends or one followed by a blank line before any member, comes back as class_doc`,
17645
+ summary: `functions (each parameter's default the source text after its =, so = null reads "null", and null for a parameter with none), variables (static ones marked is_static; one written with := has the type of a literal or built-in constructor it is given, marked inferred, as a constant always does), signals, constants, enums, inner_classes (each with its name, line and extends, and its own members in these same fields), class_name, extends and line_count, read from the file, and dependencies: the paths it loads or preloads as written, by a constant it declares too, and the paths it or an inner class extends by, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies. A member or inner class with a ## documentation comment directly above it carries the comment's text as doc, and the script's own, the ## block above class_name or extends or one followed by a blank line before any member, comes back as class_doc`,
17549
17646
  requires: []
17550
17647
  },
17551
17648
  symbols: {
@@ -18056,7 +18153,7 @@ var init_tool_definitions = __esm(() => {
18056
18153
  property: {
18057
18154
  type: "string",
18058
18155
  ops: ["property", "find"],
18059
- description: `property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A key holding a colon, a bracket or a quote is written in double quotes, 'stock:"base:dew"', as a refusal listing the keys writes it. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A vector, a colour, a rectangle or a transform is stepped into by its components as GDScript names them, so one axis is one number: "size:x", "global_position:y", "modulate:a", "global_transform:origin:z"; a name the value does not have is refused, naming the ones it has. It also answers the calls that read it with no arguments, by its type, such as "linear_velocity:length()" or "global_transform:get_origin()"; one it does not answer is refused, naming those it does. A string answers such calls too, "text:length()" or "text:strip_edges()", and "text:to_utf32_buffer():to_int32_array()" gives its code points, which tell a non-breaking space (160) from the space an answer shows in its place. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control's theme is read where it is drawn: 'get_theme_stylebox("panel"):content_margin_top' or 'get_theme_constant("separation")'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.`
18156
+ description: `property: which one to read, or a constant the node's script or a script it extends declares. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A key holding a colon, a bracket or a quote is written in double quotes, 'stock:"base:dew"', as a refusal listing the keys writes it. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A vector, a colour, a rectangle or a transform is stepped into by its components as GDScript names them, so one axis is one number: "size:x", "global_position:y", "modulate:a", "global_transform:origin:z"; a name the value does not have is refused, naming the ones it has. It also answers the calls that read it with no arguments, by its type, such as "linear_velocity:length()" or "global_transform:get_origin()"; one it does not answer is refused, naming those it does. A string answers such calls too, "text:length()" or "text:strip_edges()", and "text:to_utf32_buffer():to_int32_array()" gives its code points, which tell a non-breaking space (160) from the space an answer shows in its place. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control's theme is read where it is drawn: 'get_theme_stylebox("panel"):content_margin_top' or 'get_theme_constant("separation")'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.`
18060
18157
  },
18061
18158
  depth: {
18062
18159
  type: "integer",
@@ -18814,7 +18911,7 @@ var init_tool_reference = __esm(() => {
18814
18911
 
18815
18912
  // src/suite-plan.ts
18816
18913
  import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync21 } from "node:fs";
18817
- import { dirname as dirname11, join as join25, posix as posix3 } from "node:path";
18914
+ import { dirname as dirname11, join as join26, posix as posix3 } from "node:path";
18818
18915
  function within(path, folder) {
18819
18916
  return path === folder || path.startsWith(`${folder}/`);
18820
18917
  }
@@ -18830,7 +18927,7 @@ class ProjectScripts {
18830
18927
  this.projectPath = projectPath;
18831
18928
  let settings = {};
18832
18929
  try {
18833
- settings = parseProjectGodot(readFileSync21(join25(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18930
+ settings = parseProjectGodot(readFileSync21(join26(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18834
18931
  } catch {}
18835
18932
  const [section, key] = [NOT_SEARCHED_SETTING, GUESS_WINDOW_SETTING].map((setting) => setting.slice("gdharness/".length));
18836
18933
  const listed = settings[section ?? ""];
@@ -40848,12 +40945,12 @@ var init_stdio = __esm(() => {
40848
40945
 
40849
40946
  // src/autoload-failure.ts
40850
40947
  import { existsSync as existsSync24, readFileSync as readFileSync24 } from "node:fs";
40851
- import { join as join28 } from "node:path";
40948
+ import { join as join29 } from "node:path";
40852
40949
  function scriptsAutoloaded(projectPath) {
40853
40950
  const scripts = new Map;
40854
40951
  let settings;
40855
40952
  try {
40856
- settings = parseProjectGodot(readFileSync24(join28(projectPath, "project.godot"), "utf8"));
40953
+ settings = parseProjectGodot(readFileSync24(join29(projectPath, "project.godot"), "utf8"));
40857
40954
  } catch {
40858
40955
  return scripts;
40859
40956
  }
@@ -40862,7 +40959,7 @@ function scriptsAutoloaded(projectPath) {
40862
40959
  scripts.set(`res://${path}`, name);
40863
40960
  continue;
40864
40961
  }
40865
- const scene = join28(projectPath, path);
40962
+ const scene = join29(projectPath, path);
40866
40963
  if (!existsSync24(scene)) {
40867
40964
  continue;
40868
40965
  }
@@ -40889,7 +40986,7 @@ var init_autoload_failure = __esm(() => {
40889
40986
  import { existsSync as existsSync25, readdirSync as readdirSync15, readFileSync as readFileSync25, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
40890
40987
  import { createConnection as createConnection2 } from "node:net";
40891
40988
  import { tmpdir as tmpdir5 } from "node:os";
40892
- import { dirname as dirname14, join as join29, resolve as resolve11 } from "node:path";
40989
+ import { dirname as dirname14, join as join30, resolve as resolve11 } from "node:path";
40893
40990
  import { setTimeout as delay5 } from "node:timers/promises";
40894
40991
  function runtimeDirectory(variables = process.env) {
40895
40992
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -40897,19 +40994,19 @@ function runtimeDirectory(variables = process.env) {
40897
40994
  return explicit;
40898
40995
  }
40899
40996
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
40900
- return join29(perUser ?? tmpdir5(), "gdharness");
40997
+ return join30(perUser ?? tmpdir5(), "gdharness");
40901
40998
  }
40902
40999
  function runtimeDirectories(variables = process.env) {
40903
41000
  const candidates = [runtimeDirectory(variables)];
40904
41001
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
40905
41002
  for (const base of fallbacks) {
40906
- candidates.push(join29(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
41003
+ candidates.push(join30(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
40907
41004
  }
40908
41005
  return [...new Set(candidates.map((path) => resolve11(path)))];
40909
41006
  }
40910
41007
  function errorReportOf(pid, directories = runtimeDirectories()) {
40911
41008
  for (const directory of directories) {
40912
- const path = join29(directory, `runtime-${pid}.log`);
41009
+ const path = join30(directory, `runtime-${pid}.log`);
40913
41010
  if (existsSync25(path)) {
40914
41011
  return path;
40915
41012
  }
@@ -41065,14 +41162,14 @@ function announcedIn(directory) {
41065
41162
  for (const entry of readdirSync15(directory)) {
41066
41163
  const report = ERROR_REPORT_PATTERN.exec(entry);
41067
41164
  if (report) {
41068
- sweepErrorReport(join29(directory, entry), Number.parseInt(report[1] ?? "", 10));
41165
+ sweepErrorReport(join30(directory, entry), Number.parseInt(report[1] ?? "", 10));
41069
41166
  continue;
41070
41167
  }
41071
41168
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
41072
41169
  if (!match) {
41073
41170
  continue;
41074
41171
  }
41075
- const file = join29(directory, entry);
41172
+ const file = join30(directory, entry);
41076
41173
  const pid = Number.parseInt(match[1] ?? "", 10);
41077
41174
  const running = alive(pid);
41078
41175
  entries.push({ file, pid, running });
@@ -41100,7 +41197,7 @@ function announcedIn(directory) {
41100
41197
  found.push(announced);
41101
41198
  }
41102
41199
  const held = new Set(entries.map((one) => one.file));
41103
- const here = join29(directory, ".");
41200
+ const here = join30(directory, ".");
41104
41201
  for (const file of verdicts.keys()) {
41105
41202
  if (dirname14(file) === here && !held.has(file)) {
41106
41203
  verdicts.delete(file);
@@ -41109,7 +41206,7 @@ function announcedIn(directory) {
41109
41206
  return found;
41110
41207
  }
41111
41208
  async function announcementEnded(pid, directories = runtimeDirectories()) {
41112
- const files = directories.map((directory) => join29(directory, `runtime-${pid}.json`)).filter((file) => existsSync25(file));
41209
+ const files = directories.map((directory) => join30(directory, `runtime-${pid}.json`)).filter((file) => existsSync25(file));
41113
41210
  const first = files[0];
41114
41211
  if (first === undefined) {
41115
41212
  return;
@@ -41529,9 +41626,9 @@ var init_runtime_client = __esm(() => {
41529
41626
 
41530
41627
  // src/boot-note.ts
41531
41628
  import { mkdirSync as mkdirSync11, readFileSync as readFileSync26, writeFileSync as writeFileSync13 } from "node:fs";
41532
- import { dirname as dirname15, join as join30 } from "node:path";
41629
+ import { dirname as dirname15, join as join31 } from "node:path";
41533
41630
  function bootNotePath(projectPath) {
41534
- return join30(projectPath, ".godot", "gdharness-boot.json");
41631
+ return join31(projectPath, ".godot", "gdharness-boot.json");
41535
41632
  }
41536
41633
  function readScenes(projectPath) {
41537
41634
  try {
@@ -41578,9 +41675,9 @@ var init_boot_note = __esm(() => {
41578
41675
 
41579
41676
  // src/breakpoint-note.ts
41580
41677
  import { mkdirSync as mkdirSync12, readFileSync as readFileSync27, rmSync as rmSync13, writeFileSync as writeFileSync14 } from "node:fs";
41581
- import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as relative6 } from "node:path";
41678
+ import { dirname as dirname16, isAbsolute as isAbsolute7, join as join32, relative as relative6 } from "node:path";
41582
41679
  function breakpointNotePath(projectPath) {
41583
- return join31(projectPath, ".godot", "gdharness-breakpoints.json");
41680
+ return join32(projectPath, ".godot", "gdharness-breakpoints.json");
41584
41681
  }
41585
41682
  function writeBreakpointNote(projectPath, held) {
41586
41683
  const path = breakpointNotePath(projectPath);
@@ -41621,7 +41718,7 @@ function readBreakpointNote(projectPath) {
41621
41718
  }
41622
41719
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
41623
41720
  if (lines.length > 0) {
41624
- held.push({ scriptPath: realAsFarAsItExists(join31(projectPath, script)), lines });
41721
+ held.push({ scriptPath: realAsFarAsItExists(join32(projectPath, script)), lines });
41625
41722
  }
41626
41723
  }
41627
41724
  return held;
@@ -41640,9 +41737,9 @@ var init_breakpoint_note = __esm(() => {
41640
41737
 
41641
41738
  // src/class-note.ts
41642
41739
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync28 } from "node:fs";
41643
- import { dirname as dirname17, join as join32 } from "node:path";
41740
+ import { dirname as dirname17, join as join33 } from "node:path";
41644
41741
  function classNotePath(projectPath) {
41645
- return join32(projectPath, ".godot", "gdharness-classes.json");
41742
+ return join33(projectPath, ".godot", "gdharness-classes.json");
41646
41743
  }
41647
41744
  function readClassNote(projectPath) {
41648
41745
  try {
@@ -41689,9 +41786,9 @@ var init_class_note = __esm(() => {
41689
41786
 
41690
41787
  // src/config-pin.ts
41691
41788
  import { existsSync as existsSync26, readFileSync as readFileSync29 } from "node:fs";
41692
- import { join as join33 } from "node:path";
41789
+ import { join as join34 } from "node:path";
41693
41790
  function versionsNamedInConfig(projectPath) {
41694
- const config = join33(projectPath, ".mcp.json");
41791
+ const config = join34(projectPath, ".mcp.json");
41695
41792
  if (!existsSync26(config)) {
41696
41793
  return [];
41697
41794
  }
@@ -41719,9 +41816,9 @@ var init_config_pin = __esm(() => {
41719
41816
 
41720
41817
  // src/cut-test-run.ts
41721
41818
  import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync30, rmSync as rmSync14, writeFileSync as writeFileSync15 } from "node:fs";
41722
- import { dirname as dirname18, join as join34 } from "node:path";
41819
+ import { dirname as dirname18, join as join35 } from "node:path";
41723
41820
  function keptAt(projectPath) {
41724
- return join34(projectPath, ".godot", "gdharness-reports", "cut-run.json");
41821
+ return join35(projectPath, ".godot", "gdharness-reports", "cut-run.json");
41725
41822
  }
41726
41823
  function keepCutTestRun(projectPath, run) {
41727
41824
  const path = keptAt(projectPath);
@@ -41753,6 +41850,30 @@ function cutTestRunNote(run) {
41753
41850
  }
41754
41851
  var init_cut_test_run = () => {};
41755
41852
 
41853
+ // src/godot-variant.ts
41854
+ var FLAG_64, MOST_MESSAGE_BYTES;
41855
+ var init_godot_variant = __esm(() => {
41856
+ FLAG_64 = 1 << 16;
41857
+ MOST_MESSAGE_BYTES = (8 << 20) + 4;
41858
+ });
41859
+
41860
+ // src/game-debugger.ts
41861
+ var PAUSE_MS = 30000, STEP_MS = 1e4, RUNTIME_ADDON2 = "res://addons/gdharness_runtime/", RUNTIME_AUTOLOAD2, READ_MS = 5000, EVALUATE_MS = 60000, REPLIES;
41862
+ var init_game_debugger = __esm(() => {
41863
+ init_godot_variant();
41864
+ init_setup();
41865
+ RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON2}runtime_autoload.gd`;
41866
+ REPLIES = new Set([
41867
+ "debug_enter",
41868
+ "debug_exit",
41869
+ "stack_dump",
41870
+ "stack_frame_vars",
41871
+ "stack_frame_var",
41872
+ "evaluation_return",
41873
+ "scene:inspect_object"
41874
+ ]);
41875
+ });
41876
+
41756
41877
  // src/dap_client.ts
41757
41878
  import { createConnection as createConnection3 } from "node:net";
41758
41879
  import { setTimeout as delay6 } from "node:timers/promises";
@@ -41795,11 +41916,13 @@ class GodotDAPClient {
41795
41916
  onBreakpointsChanged = null;
41796
41917
  scopesPatienceMs;
41797
41918
  stepPatienceMs;
41919
+ evaluatePatienceMs;
41798
41920
  constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1", patience = {}) {
41799
41921
  this.port = port;
41800
41922
  this.host = host;
41801
41923
  this.scopesPatienceMs = patience.scopesMs ?? SCOPES_TIMEOUT_MS;
41802
41924
  this.stepPatienceMs = patience.stepMs ?? STEP_LANDS_WITHIN_MS;
41925
+ this.evaluatePatienceMs = patience.evaluateMs ?? EVALUATE_MS;
41803
41926
  this.pendingRequests = new Map;
41804
41927
  }
41805
41928
  async connect() {
@@ -42303,7 +42426,7 @@ class GodotDAPClient {
42303
42426
  if (members?.variables?.some((variable) => variable["name"] === "self") !== true) {
42304
42427
  throw new ReadRefusal(`Frame ${frame} has no instance of its script to evaluate on, as a static function called on its class has none, and the engine lets the game go on rather than answer one there, so it was not asked. Evaluate in a frame below it, or read this one's locals with debug_state variables`);
42305
42428
  }
42306
- const deadline = Date.now() + this.scopesPatienceMs;
42429
+ const deadline = Date.now() + this.evaluatePatienceMs;
42307
42430
  while (Date.now() < deadline) {
42308
42431
  try {
42309
42432
  const answered = await this.sendRequest("evaluate", { expression, frameId: frame, context: "watch" }, VARIABLES_POLL_TIMEOUT_MS);
@@ -42315,7 +42438,7 @@ class GodotDAPClient {
42315
42438
  } catch {}
42316
42439
  await delay6(200);
42317
42440
  }
42318
- throw new ReadRefusal(`What ${expression} comes to did not arrive from the game within ${this.scopesPatienceMs / 1000}s. It comes from the game through the editor: ask again`);
42441
+ throw new ReadRefusal(`What ${expression} comes to did not arrive from the game within ${this.evaluatePatienceMs / 1000}s. It comes from the game through the editor, and the first expression a game evaluates loads the script of every global class in the project first, static initialisers included, so one of those may still be running: ask again`);
42319
42442
  }
42320
42443
  isConnected() {
42321
42444
  return this.connected;
@@ -42462,6 +42585,7 @@ var init_dap_client = __esm(() => {
42462
42585
  init_answer_json();
42463
42586
  init_errors();
42464
42587
  init_framing();
42588
+ init_game_debugger();
42465
42589
  init_ports();
42466
42590
  ReadRefusal = class ReadRefusal extends Refusal {
42467
42591
  };
@@ -42479,12 +42603,12 @@ import {
42479
42603
  statSync as statSync16,
42480
42604
  writeFileSync as writeFileSync16
42481
42605
  } from "node:fs";
42482
- import { dirname as dirname19, join as join35 } from "node:path";
42606
+ import { dirname as dirname19, join as join36 } from "node:path";
42483
42607
  function editorLogPath(projectPath) {
42484
- return join35(projectPath, ".godot", "gdharness-editor.log");
42608
+ return join36(projectPath, ".godot", "gdharness-editor.log");
42485
42609
  }
42486
42610
  function editorLogNotePath(projectPath) {
42487
- return join35(projectPath, ".godot", "gdharness-editor.json");
42611
+ return join36(projectPath, ".godot", "gdharness-editor.json");
42488
42612
  }
42489
42613
  function readEditorLogNote(projectPath) {
42490
42614
  try {
@@ -43523,7 +43647,7 @@ var init_rename = __esm(() => {
43523
43647
 
43524
43648
  // src/rename-symbol.ts
43525
43649
  import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync16, realpathSync as realpathSync4, rmSync as rmSync16, writeFileSync as writeFileSync18 } from "node:fs";
43526
- import { basename as basename9, dirname as dirname21, join as join36, relative as relative7, resolve as resolve12 } from "node:path";
43650
+ import { basename as basename9, dirname as dirname21, join as join37, relative as relative7, resolve as resolve12 } from "node:path";
43527
43651
  function resourcePathOf(projectPath, absolute) {
43528
43652
  const inside = relative7(onDiskSpelling(projectPath), onDiskSpelling(absolute));
43529
43653
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -43536,7 +43660,7 @@ function onDiskSpelling(path) {
43536
43660
  let at = resolve12(path);
43537
43661
  for (;; ) {
43538
43662
  try {
43539
- return join36(realpathSync4.native(at), ...missing.reverse());
43663
+ return join37(realpathSync4.native(at), ...missing.reverse());
43540
43664
  } catch {
43541
43665
  const parent = dirname21(at);
43542
43666
  if (parent === at) {
@@ -43974,15 +44098,15 @@ ${mention.kind}`;
43974
44098
  };
43975
44099
  }
43976
44100
  function writeLeftAloneDetail(projectPath, symbol, detail) {
43977
- const directory = join36(projectPath, ".godot", "gdharness-renames");
44101
+ const directory = join37(projectPath, ".godot", "gdharness-renames");
43978
44102
  mkdirSync17(directory, { recursive: true });
43979
44103
  detailWrites += 1;
43980
- const path = join36(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
44104
+ const path = join37(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
43981
44105
  writeFileSync18(path, `${JSON.stringify(detail, null, 2)}
43982
44106
  `, "utf8");
43983
44107
  const kept = readdirSync16(directory).filter((name) => name.endsWith(".json")).sort().reverse();
43984
44108
  for (const old of kept.slice(10)) {
43985
- rmSync16(join36(directory, old), { force: true });
44109
+ rmSync16(join37(directory, old), { force: true });
43986
44110
  }
43987
44111
  return path;
43988
44112
  }
@@ -44031,10 +44155,10 @@ import {
44031
44155
  statSync as statSync17,
44032
44156
  writeFileSync as writeFileSync19
44033
44157
  } from "node:fs";
44034
- import { basename as basename10, dirname as dirname22, extname as extname3, join as join37, relative as relative8 } from "node:path";
44158
+ import { basename as basename10, dirname as dirname22, extname as extname4, join as join38, relative as relative8 } from "node:path";
44035
44159
  function filesUnder(directory) {
44036
44160
  return readdirSync17(directory, { withFileTypes: true }).flatMap((entry) => {
44037
- const path = join37(directory, entry.name);
44161
+ const path = join38(directory, entry.name);
44038
44162
  return entry.isDirectory() ? filesUnder(path) : [path];
44039
44163
  });
44040
44164
  }
@@ -44055,8 +44179,8 @@ function unseenByTheEngine(projectPath, path) {
44055
44179
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
44056
44180
  }
44057
44181
  if (at < steps.length - 1) {
44058
- directory = join37(directory, step);
44059
- if (existsSync31(join37(directory, ".gdignore"))) {
44182
+ directory = join38(directory, step);
44183
+ if (existsSync31(join38(directory, ".gdignore"))) {
44060
44184
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
44061
44185
  }
44062
44186
  }
@@ -44068,12 +44192,12 @@ function installedAddonOf(projectPath, path) {
44068
44192
  if (steps[0] !== "addons" || steps[1] === undefined) {
44069
44193
  return null;
44070
44194
  }
44071
- return existsSync31(join37(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
44195
+ return existsSync31(join38(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
44072
44196
  }
44073
44197
  function binaryResources(projectPath) {
44074
44198
  const found = [];
44075
44199
  const visit = (directory, prefix) => {
44076
- if (existsSync31(join37(directory, ".gdignore"))) {
44200
+ if (existsSync31(join38(directory, ".gdignore"))) {
44077
44201
  return;
44078
44202
  }
44079
44203
  for (const entry of readdirSync17(directory, { withFileTypes: true })) {
@@ -44082,7 +44206,7 @@ function binaryResources(projectPath) {
44082
44206
  }
44083
44207
  const path = `${prefix}${entry.name}`;
44084
44208
  if (entry.isDirectory()) {
44085
- visit(join37(directory, entry.name), `${path}/`);
44209
+ visit(join38(directory, entry.name), `${path}/`);
44086
44210
  } else if (/\.(scn|res)$/i.test(entry.name)) {
44087
44211
  found.push(path);
44088
44212
  }
@@ -44092,7 +44216,7 @@ function binaryResources(projectPath) {
44092
44216
  return found;
44093
44217
  }
44094
44218
  function targetPathOf(projectPath, absolute) {
44095
- const inside = relative8(onDiskSpelling(projectPath), join37(onDiskSpelling(dirname22(absolute)), basename10(absolute)));
44219
+ const inside = relative8(onDiskSpelling(projectPath), join38(onDiskSpelling(dirname22(absolute)), basename10(absolute)));
44096
44220
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
44097
44221
  return null;
44098
44222
  }
@@ -44147,14 +44271,14 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44147
44271
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
44148
44272
  }
44149
44273
  if (!isDirectory) {
44150
- const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync31(join37(projectPath, path.slice("res://".length))));
44274
+ const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync31(join38(projectPath, path.slice("res://".length))));
44151
44275
  if (owner !== undefined && owner !== null) {
44152
44276
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
44153
44277
  }
44154
- if (extname3(from).toLowerCase() !== extname3(to).toLowerCase()) {
44278
+ if (extname4(from).toLowerCase() !== extname4(to).toLowerCase()) {
44155
44279
  return {
44156
44280
  ok: false,
44157
- reason: `${from} would become ${to}, and the engine picks how to read a file by its extension: give newPath the extension it has, ${extname3(from) || "none"}.`
44281
+ reason: `${from} would become ${to}, and the engine picks how to read a file by its extension: give newPath the extension it has, ${extname4(from) || "none"}.`
44158
44282
  };
44159
44283
  }
44160
44284
  }
@@ -44181,12 +44305,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44181
44305
  scripts,
44182
44306
  moves,
44183
44307
  named: [from],
44184
- exists: (path) => existsSync31(join37(projectPath, path.slice("res://".length)))
44308
+ exists: (path) => existsSync31(join38(projectPath, path.slice("res://".length)))
44185
44309
  }, { edits: [], mentions: [] });
44186
44310
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
44187
44311
  const binaries = [];
44188
44312
  for (const path of binaryResources(projectPath)) {
44189
- const bytes = readFileSync33(join37(projectPath, path.slice("res://".length)));
44313
+ const bytes = readFileSync33(join38(projectPath, path.slice("res://".length)));
44190
44314
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
44191
44315
  if (names !== undefined) {
44192
44316
  binaries.push({
@@ -44205,7 +44329,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44205
44329
  file: afterwards(mention.file)
44206
44330
  }));
44207
44331
  const left = leftAloneReport(mentions);
44208
- const absoluteOf = (path) => join37(projectPath, path.slice("res://".length));
44332
+ const absoluteOf = (path) => join38(projectPath, path.slice("res://".length));
44209
44333
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
44210
44334
  const moved = [...moves].map(([source, target]) => {
44211
44335
  const rewrite = rewritten.get(source);
@@ -44311,30 +44435,6 @@ var init_file_move = __esm(() => {
44311
44435
  SIDECARS = [".uid", ".import"];
44312
44436
  });
44313
44437
 
44314
- // src/godot-variant.ts
44315
- var FLAG_64, MOST_MESSAGE_BYTES;
44316
- var init_godot_variant = __esm(() => {
44317
- FLAG_64 = 1 << 16;
44318
- MOST_MESSAGE_BYTES = (8 << 20) + 4;
44319
- });
44320
-
44321
- // src/game-debugger.ts
44322
- var PAUSE_MS = 30000, STEP_MS = 1e4, RUNTIME_ADDON2 = "res://addons/gdharness_runtime/", RUNTIME_AUTOLOAD2, READ_MS = 5000, REPLIES;
44323
- var init_game_debugger = __esm(() => {
44324
- init_godot_variant();
44325
- init_setup();
44326
- RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON2}runtime_autoload.gd`;
44327
- REPLIES = new Set([
44328
- "debug_enter",
44329
- "debug_exit",
44330
- "stack_dump",
44331
- "stack_frame_vars",
44332
- "stack_frame_var",
44333
- "evaluation_return",
44334
- "scene:inspect_object"
44335
- ]);
44336
- });
44337
-
44338
44438
  // node_modules/ws/lib/constants.js
44339
44439
  var require_constants = __commonJS(function(exports, module) {
44340
44440
  var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
@@ -47996,10 +48096,10 @@ var init_keeper_channel = () => {};
47996
48096
 
47997
48097
  // src/lifecycle-log.ts
47998
48098
  import { appendFileSync, mkdirSync as mkdirSync19, renameSync as renameSync5, statSync as statSync18 } from "node:fs";
47999
- import { dirname as dirname23, join as join38 } from "node:path";
48099
+ import { dirname as dirname23, join as join39 } from "node:path";
48000
48100
  import process16 from "node:process";
48001
48101
  function lifecycleLogPath() {
48002
- return join38(runtimeDirectory(), "servers.log");
48102
+ return join39(runtimeDirectory(), "servers.log");
48003
48103
  }
48004
48104
  function writeLifecycle(event, fields = {}) {
48005
48105
  const path = lifecycleLogPath();
@@ -48370,9 +48470,9 @@ var init_prompts = __esm(() => {
48370
48470
 
48371
48471
  // src/restart-note.ts
48372
48472
  import { mkdirSync as mkdirSync20, readFileSync as readFileSync34, rmSync as rmSync17, writeFileSync as writeFileSync20 } from "node:fs";
48373
- import { dirname as dirname24, join as join39 } from "node:path";
48473
+ import { dirname as dirname24, join as join40 } from "node:path";
48374
48474
  function restartNotePath(projectPath) {
48375
- return join39(projectPath, ".godot", "gdharness-restart.json");
48475
+ return join40(projectPath, ".godot", "gdharness-restart.json");
48376
48476
  }
48377
48477
  function noteRestartBegun(note) {
48378
48478
  const path = restartNotePath(note.projectPath);
@@ -48413,9 +48513,9 @@ var init_restart_note = __esm(() => {
48413
48513
  // src/run-record.ts
48414
48514
  import { createHash as createHash7 } from "node:crypto";
48415
48515
  import { mkdirSync as mkdirSync21, openSync as openSync5, readdirSync as readdirSync18, readFileSync as readFileSync35, rmSync as rmSync18, statSync as statSync19 } from "node:fs";
48416
- import { basename as basename11, join as join40, resolve as resolve13 } from "node:path";
48516
+ import { basename as basename11, join as join41, resolve as resolve13 } from "node:path";
48417
48517
  function runsDirectory() {
48418
- return join40(runtimeDirectory(), "runs");
48518
+ return join41(runtimeDirectory(), "runs");
48419
48519
  }
48420
48520
  function projectKey(projectPath) {
48421
48521
  const real = realAsFarAsItExists(resolve13(projectPath)).replace(/[\\/]+$/, "");
@@ -48423,10 +48523,10 @@ function projectKey(projectPath) {
48423
48523
  return createHash7("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
48424
48524
  }
48425
48525
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
48426
- return join40(directory, "runs", `note-${projectKey(projectPath)}.json`);
48526
+ return join41(directory, "runs", `note-${projectKey(projectPath)}.json`);
48427
48527
  }
48428
48528
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
48429
- return join40(directory, "runs", `latest-${projectKey(servedBy)}.json`);
48529
+ return join41(directory, "runs", `latest-${projectKey(servedBy)}.json`);
48430
48530
  }
48431
48531
  function writeLatestRun(servedBy, latest) {
48432
48532
  try {
@@ -48451,20 +48551,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
48451
48551
  }
48452
48552
  function openTranscript(startedAt) {
48453
48553
  mkdirSync21(runsDirectory(), { recursive: true });
48454
- const path = join40(runsDirectory(), `run-${startedAt}.log`);
48554
+ const path = join41(runsDirectory(), `run-${startedAt}.log`);
48455
48555
  return { path, fd: openSync5(path, "a") };
48456
48556
  }
48457
48557
  function profilePath(startedAt) {
48458
- return join40(runsDirectory(), `run-${startedAt}.profile.json`);
48558
+ return join41(runsDirectory(), `run-${startedAt}.profile.json`);
48459
48559
  }
48460
48560
  function profileMarkPath(startedAt) {
48461
- return join40(runsDirectory(), `run-${startedAt}.profile-mark.json`);
48561
+ return join41(runsDirectory(), `run-${startedAt}.profile-mark.json`);
48462
48562
  }
48463
48563
  function noteIsAbout(note, playing) {
48464
48564
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
48465
48565
  }
48466
48566
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
48467
- return join40(directory, "runs", `editor-${projectKey(projectPath)}.json`);
48567
+ return join41(directory, "runs", `editor-${projectKey(projectPath)}.json`);
48468
48568
  }
48469
48569
  function writeEditorRunNote(note) {
48470
48570
  mkdirSync21(runsDirectory(), { recursive: true });
@@ -48517,13 +48617,13 @@ function everyRunRecord() {
48517
48617
  for (const directory of runtimeDirectories()) {
48518
48618
  let entries;
48519
48619
  try {
48520
- entries = readdirSync18(join40(directory, "runs"));
48620
+ entries = readdirSync18(join41(directory, "runs"));
48521
48621
  } catch {
48522
48622
  continue;
48523
48623
  }
48524
48624
  for (const entry of entries) {
48525
48625
  if (entry.startsWith("note-") && entry.endsWith(".json")) {
48526
- const record = recordAt(join40(directory, "runs", entry));
48626
+ const record = recordAt(join41(directory, "runs", entry));
48527
48627
  if (record !== null) {
48528
48628
  found.push(record);
48529
48629
  }
@@ -48791,7 +48891,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
48791
48891
  if (!transcript && !entry.endsWith(".partial")) {
48792
48892
  continue;
48793
48893
  }
48794
- const path = join40(directory, entry);
48894
+ const path = join41(directory, entry);
48795
48895
  if (kept.has(path) || ageMs(path, now) < keepMs) {
48796
48896
  continue;
48797
48897
  }
@@ -49047,16 +49147,16 @@ var init_server_instructions = __esm(() => {
49047
49147
 
49048
49148
  // src/suites-scene.ts
49049
49149
  import { existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync21 } from "node:fs";
49050
- import { join as join41, relative as relative9, sep as sep5 } from "node:path";
49150
+ import { join as join42, relative as relative9, sep as sep5 } from "node:path";
49051
49151
  function hasSuitesScene(projectPath) {
49052
- return existsSync32(join41(projectPath, SUITES_SCENE));
49152
+ return existsSync32(join42(projectPath, SUITES_SCENE));
49053
49153
  }
49054
49154
  function suitesSceneArgs(projectPath, suites) {
49055
- const runs = join41(projectPath, ".godot", "gdharness", "suite-scenes");
49155
+ const runs = join42(projectPath, ".godot", "gdharness", "suite-scenes");
49056
49156
  sweepRunDirectories(runs);
49057
- const directory = join41(runs, `${String(STARTED_AT)}-${String(process.pid)}`);
49157
+ const directory = join42(runs, `${String(STARTED_AT)}-${String(process.pid)}`);
49058
49158
  mkdirSync22(directory, { recursive: true });
49059
- const suitesFile = join41(directory, "suites.txt");
49159
+ const suitesFile = join42(directory, "suites.txt");
49060
49160
  writeFileSync21(suitesFile, `${suites.join(`
49061
49161
  `)}
49062
49162
  `);
@@ -49071,7 +49171,7 @@ function suitesSceneArgs(projectPath, suites) {
49071
49171
  "-c",
49072
49172
  "--ignoreHeadlessMode",
49073
49173
  "-rd",
49074
- `res://${relative9(projectPath, join41(directory, "reports")).split(sep5).join("/")}`,
49174
+ `res://${relative9(projectPath, join42(directory, "reports")).split(sep5).join("/")}`,
49075
49175
  "-rc",
49076
49176
  "1"
49077
49177
  ];
@@ -49085,7 +49185,7 @@ var init_suites_scene = __esm(() => {
49085
49185
 
49086
49186
  // src/test-selection.ts
49087
49187
  import { existsSync as existsSync33 } from "node:fs";
49088
- import { basename as basename12, join as join42 } from "node:path";
49188
+ import { basename as basename12, join as join43 } from "node:path";
49089
49189
  function casesOutOfPath(projectPath, given, tests) {
49090
49190
  const entries = Array.isArray(given) ? given : [given];
49091
49191
  const cases = [];
@@ -49129,12 +49229,12 @@ function casesIn(scripts, suite) {
49129
49229
  }
49130
49230
  function scriptsWithoutCases(projectPath, asked) {
49131
49231
  const scripts = new ProjectScripts(projectPath);
49132
- return asked.filter((path) => path.endsWith(".gd") && existsSync33(join42(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
49232
+ return asked.filter((path) => path.endsWith(".gd") && existsSync33(join43(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
49133
49233
  }
49134
49234
  function suitesUnder(projectPath, asked) {
49135
49235
  const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
49136
49236
  return [
49137
- ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync33(join42(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
49237
+ ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync33(join43(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
49138
49238
  ];
49139
49239
  }
49140
49240
  function selectTests(projectPath, asked, given) {
@@ -49212,7 +49312,7 @@ import {
49212
49312
  statSync as statSync20,
49213
49313
  writeFileSync as writeFileSync22
49214
49314
  } from "node:fs";
49215
- import { basename as basename13, dirname as dirname25, extname as extname4, isAbsolute as isAbsolute8, join as join43, normalize as normalize2, relative as relative10 } from "node:path";
49315
+ import { basename as basename13, dirname as dirname25, extname as extname5, isAbsolute as isAbsolute8, join as join44, normalize as normalize2, relative as relative10 } from "node:path";
49216
49316
  import { setTimeout as delay7 } from "node:timers/promises";
49217
49317
  import { fileURLToPath as fileURLToPath5 } from "node:url";
49218
49318
  function editorRestartTimeoutMs() {
@@ -49264,14 +49364,14 @@ function writtenFileRefusal(path, rule) {
49264
49364
  if (!isAbsolute8(path)) {
49265
49365
  return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
49266
49366
  }
49267
- if (!rule.extensions.includes(extname4(path).toLowerCase())) {
49367
+ if (!rule.extensions.includes(extname5(path).toLowerCase())) {
49268
49368
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
49269
49369
  }
49270
49370
  const directory = realAsFarAsItExists(dirname25(path));
49271
49371
  if (!existsSync34(directory)) {
49272
49372
  return `${named}'s directory ${dirname25(path)} does not exist; ${what} creates no directories.`;
49273
49373
  }
49274
- const where = join43(directory, basename13(path));
49374
+ const where = join44(directory, basename13(path));
49275
49375
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realAsFarAsItExists(project), where));
49276
49376
  if (inside !== undefined) {
49277
49377
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
@@ -49296,7 +49396,7 @@ function movieRefusal(engineArgs, projects, headless) {
49296
49396
  extensions: [".avi", ".png"],
49297
49397
  projects,
49298
49398
  writes: (where) => {
49299
- if (extname4(where).toLowerCase() !== ".png") {
49399
+ if (extname5(where).toLowerCase() !== ".png") {
49300
49400
  return [where];
49301
49401
  }
49302
49402
  const base = where.slice(0, -".png".length);
@@ -49611,16 +49711,6 @@ function endedPreviousRun(ended) {
49611
49711
  function previousRunLeft(ended) {
49612
49712
  return (ended?.ending === "refused" || ended?.ending === "lingering") && ended.pid !== null ? ended.pid : undefined;
49613
49713
  }
49614
- function editorsOfProjectIn(tree, projectPath) {
49615
- const found = [];
49616
- for (const [pid, listed] of tree) {
49617
- const read = readCommandLine(listed.command);
49618
- if (read.editor && read.oneShot === null && read.projectPath !== null && /godot/i.test(read.executable) && !CONSOLE_WRAPPER.test(read.executable) && isSameDirectory(read.projectPath, projectPath)) {
49619
- found.push(pid);
49620
- }
49621
- }
49622
- return found.sort((a, b) => a - b);
49623
- }
49624
49714
  function engineNamesOf(executable) {
49625
49715
  return CONSOLE_WRAPPER.test(executable) ? [executable, executable.replace(CONSOLE_WRAPPER, "$1")] : [executable];
49626
49716
  }
@@ -49918,7 +50008,7 @@ function notRunNote(notRun, failFast) {
49918
50008
  function presetsIn(projectPath) {
49919
50009
  let text;
49920
50010
  try {
49921
- text = readFileSync37(join43(projectPath, "export_presets.cfg"), "utf8");
50011
+ text = readFileSync37(join44(projectPath, "export_presets.cfg"), "utf8");
49922
50012
  } catch {
49923
50013
  return null;
49924
50014
  }
@@ -49999,7 +50089,7 @@ function filesIn(folder) {
49999
50089
  try {
50000
50090
  for (const entry of readdirSync19(folder, { withFileTypes: true })) {
50001
50091
  if (entry.isFile()) {
50002
- found.set(entry.name, statSync20(join43(folder, entry.name)).mtimeMs);
50092
+ found.set(entry.name, statSync20(join44(folder, entry.name)).mtimeMs);
50003
50093
  }
50004
50094
  }
50005
50095
  } catch {}
@@ -50039,7 +50129,7 @@ function unsavedInEditorNote(scripts) {
50039
50129
  return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
50040
50130
  }
50041
50131
  function editorAddonMissing(projectPath) {
50042
- if (!existsSync34(join43(projectPath, "project.godot"))) {
50132
+ if (!existsSync34(join44(projectPath, "project.godot"))) {
50043
50133
  return null;
50044
50134
  }
50045
50135
  const report = inspectProject(projectPath);
@@ -50067,7 +50157,7 @@ function rewriteBusNamed(projectPath, from, to) {
50067
50157
  const giving = [`bus = &"${quoted(from)}"`, `bus = "${quoted(from)}"`];
50068
50158
  const rewritten = [];
50069
50159
  for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
50070
- const absolute = join43(projectPath, file);
50160
+ const absolute = join44(projectPath, file);
50071
50161
  const lines = readFileSync37(absolute, "utf8").split(`
50072
50162
  `);
50073
50163
  const at = [];
@@ -50087,7 +50177,7 @@ function rewriteBusNamed(projectPath, from, to) {
50087
50177
  }
50088
50178
  const namedInScripts = [];
50089
50179
  for (const file of projectFiles(projectPath, [".gd"])) {
50090
- readFileSync37(join43(projectPath, file), "utf8").split(`
50180
+ readFileSync37(join44(projectPath, file), "utf8").split(`
50091
50181
  `).forEach((line, index) => {
50092
50182
  if (line.includes(`"${from}"`)) {
50093
50183
  namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
@@ -50372,7 +50462,7 @@ function profileFrameFunctions(projectPath) {
50372
50462
  let projectGodot = null;
50373
50463
  if (projectPath !== null) {
50374
50464
  try {
50375
- projectGodot = readFileSync37(join43(projectPath, "project.godot"), "utf8");
50465
+ projectGodot = readFileSync37(join44(projectPath, "project.godot"), "utf8");
50376
50466
  } catch {}
50377
50467
  }
50378
50468
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -50568,7 +50658,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
50568
50658
  }
50569
50659
  function gdUnitFailsOnScriptErrors(projectPath) {
50570
50660
  try {
50571
- return settingKeys(readFileSync37(join43(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
50661
+ return settingKeys(readFileSync37(join44(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
50572
50662
  } catch {
50573
50663
  return true;
50574
50664
  }
@@ -50583,7 +50673,7 @@ function uncaughtNote(uncaught) {
50583
50673
  }
50584
50674
  function elsewhereIn(projectPath, asked) {
50585
50675
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
50586
- if (wanted === "" || existsSync34(join43(projectPath, wanted))) {
50676
+ if (wanted === "" || existsSync34(join44(projectPath, wanted))) {
50587
50677
  return "";
50588
50678
  }
50589
50679
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -50606,7 +50696,7 @@ function windowNoteFor(needing, sharing) {
50606
50696
  return `${named(needing)} ${one ? "needs" : "need"} a window, as gdharness test reads ${one ? "it" : "them"}, so the run had one: the engine ran without --headless, at the size project.godot gives its viewport, with no sound${process.platform === "win32" ? ", on a desktop of its own, where its window neither shows nor takes the keyboard" : ""}. ${why}${shared}`;
50607
50697
  }
50608
50698
  function testPathsIn(projectPath, given, named = "path") {
50609
- const defaults = TEST_DIRECTORIES.filter((directory) => existsSync34(join43(projectPath, directory)));
50699
+ const defaults = TEST_DIRECTORIES.filter((directory) => existsSync34(join44(projectPath, directory)));
50610
50700
  const entries = given === undefined || given === null || given === "" ? defaults.length > 0 ? defaults : TEST_DIRECTORIES : given;
50611
50701
  const listed = Array.isArray(entries) ? entries : [entries];
50612
50702
  if (listed.length === 0) {
@@ -50737,7 +50827,7 @@ function needsItsOwnProcess(asked) {
50737
50827
  class GodotServer {
50738
50828
  stdio = null;
50739
50829
  locator = new GodotLocator;
50740
- operationsScript = join43(__dirname2, "godot", "operations", "godot_operations.gd");
50830
+ operationsScript = join44(__dirname2, "godot", "operations", "godot_operations.gd");
50741
50831
  exportOptionsRead = new Map;
50742
50832
  heldWhilePending = new Set;
50743
50833
  evaluatedWhilePending = new Set;
@@ -51009,7 +51099,7 @@ class GodotServer {
51009
51099
  }
51010
51100
  noteTheStart() {
51011
51101
  const cwd = process.cwd();
51012
- const mcpJson = join43(cwd, ".mcp.json");
51102
+ const mcpJson = join44(cwd, ".mcp.json");
51013
51103
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
51014
51104
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
51015
51105
  writeLifecycle("started", {
@@ -51157,7 +51247,7 @@ class GodotServer {
51157
51247
  return checked.response;
51158
51248
  }
51159
51249
  const named = args["projectPath"];
51160
- if (typeof named === "string" && existsSync34(join43(named, "project.godot"))) {
51250
+ if (typeof named === "string" && existsSync34(join44(named, "project.godot"))) {
51161
51251
  this.lastProjectPath = named;
51162
51252
  }
51163
51253
  this.updates.refresh();
@@ -51200,7 +51290,7 @@ class GodotServer {
51200
51290
  let notSaved;
51201
51291
  try {
51202
51292
  savedAt = answer.content.map((item, index) => {
51203
- const file = join43(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
51293
+ const file = join44(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
51204
51294
  writeFileSync22(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
51205
51295
  return file;
51206
51296
  });
@@ -51897,7 +51987,7 @@ class GodotServer {
51897
51987
  if (path === undefined) {
51898
51988
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
51899
51989
  }
51900
- const file = join43(path, "project.godot");
51990
+ const file = join44(path, "project.godot");
51901
51991
  if (!existsSync34(file)) {
51902
51992
  return {
51903
51993
  ok: false,
@@ -52634,6 +52724,9 @@ class GodotServer {
52634
52724
  if (forValidation.length > 0 && !include.includes("validation")) {
52635
52725
  return this.createErrorResponse(`${forValidation.join(" and ")} ${forValidation.length === 1 ? "is" : "are"} read only by the validation section, which this call does not include: add "validation" to include, or leave ${forValidation.length === 1 ? "it" : "them"} out.`);
52636
52726
  }
52727
+ if (args["addonClasses"] !== undefined && !include.includes("classes")) {
52728
+ return this.createErrorResponse('addonClasses is read only by the classes section, which this call does not include: add "classes" to include, or leave it out.');
52729
+ }
52637
52730
  const project = this.project(args);
52638
52731
  if (!project.ok) {
52639
52732
  return project.response;
@@ -52664,6 +52757,23 @@ class GodotServer {
52664
52757
  ...compiledScripts !== undefined && (section === "health" || section === "validation") ? { compiled: compiledScripts } : {}
52665
52758
  };
52666
52759
  const outcome = await this.operation(spec.operation, params, project.value.path);
52760
+ if (section === "classes" && outcome.ok && Array.isArray(outcome.payload["classes"])) {
52761
+ const declared = outcome.payload["classes"];
52762
+ const asked = readStringArray(args, "addonClasses") ?? [];
52763
+ const addons = Object.keys(classesWithAddonsCounted(declared, new Set).inAddons).sort();
52764
+ const unknownAddons = asked.filter((addon) => !addons.includes(addon));
52765
+ if (unknownAddons.length > 0) {
52766
+ return this.createErrorResponse(`addonClasses names ${unknownAddons.join(", ")}, and no folder of that name under res://addons declares a class. ${addons.length === 0 ? "No addon declares one." : `The addons declaring classes are ${addons.join(", ")}.`}`);
52767
+ }
52768
+ const counted = classesWithAddonsCounted(declared, new Set(asked));
52769
+ info[section] = {
52770
+ ...outcome.payload,
52771
+ classes: counted.classes,
52772
+ ...Object.keys(counted.inAddons).length > 0 ? { inAddons: counted.inAddons } : {},
52773
+ ...engineExtras(outcome)
52774
+ };
52775
+ continue;
52776
+ }
52667
52777
  info[section] = outcome.ok ? { ...outcome.payload, ...engineExtras(outcome) } : { error: outcome.message, ...engineExtras(outcome) };
52668
52778
  }
52669
52779
  return this.jsonTextResponse(info);
@@ -52779,7 +52889,7 @@ class GodotServer {
52779
52889
  const platform = presetPlatform(project.value.path, preset);
52780
52890
  if (platform !== null) {
52781
52891
  const known = await this.exportOptions(platform, {
52782
- presetsFile: join43(project.value.path, "export_presets.cfg"),
52892
+ presetsFile: join44(project.value.path, "export_presets.cfg"),
52783
52893
  preset,
52784
52894
  asked: options
52785
52895
  });
@@ -52830,7 +52940,7 @@ ${platform}`;
52830
52940
  }
52831
52941
  const empty = scratchDirectory("export");
52832
52942
  try {
52833
- writeFileSync22(join43(empty, "project.godot"), `config_version=5
52943
+ writeFileSync22(join44(empty, "project.godot"), `config_version=5
52834
52944
  `);
52835
52945
  const outcome = await runOperation({ godotPath: engine.value, script: this.operationsScript, debug: DEBUG_MODE, editor: true }, "export_options", { platform, ...standing }, empty);
52836
52946
  const options = outcome.ok ? outcome.payload["options"] : undefined;
@@ -52878,7 +52988,7 @@ ${platform}`;
52878
52988
  if (pack && debug) {
52879
52989
  return this.createErrorResponse("A pack is exported as a release: the command-line exporter has no debug pack, so debug and pack cannot both be true.", ["Leave debug out for a pack", "Or leave pack out for a debug build"]);
52880
52990
  }
52881
- const extension = extname4(output.relativePath).toLowerCase();
52991
+ const extension = extname5(output.relativePath).toLowerCase();
52882
52992
  if (pack && extension !== ".pck" && extension !== ".zip") {
52883
52993
  return this.createErrorResponse(`A pack is written as a .pck or a .zip, and ${output.relativePath} is neither: the exporter picks the format by the extension.`, ["Give outputPath ending in .pck, or .zip for a zip of the resources"]);
52884
52994
  }
@@ -52886,7 +52996,7 @@ ${platform}`;
52886
52996
  const exportArgs = [
52887
52997
  "--headless",
52888
52998
  "--log-file",
52889
- join43(exportLogs, "engine.log"),
52999
+ join44(exportLogs, "engine.log"),
52890
53000
  "--path",
52891
53001
  project.value.path,
52892
53002
  pack ? "--export-pack" : debug ? "--export-debug" : "--export-release",
@@ -52985,7 +53095,7 @@ ${platform}`;
52985
53095
  if (movie !== null) {
52986
53096
  return this.createErrorResponse(movie);
52987
53097
  }
52988
- const missing = paths.value.filter((path) => !existsSync34(join43(project.value.path, path.slice("res://".length))));
53098
+ const missing = paths.value.filter((path) => !existsSync34(join44(project.value.path, path.slice("res://".length))));
52989
53099
  const projectScripts = new ProjectScripts(project.value.path);
52990
53100
  const present = paths.value.filter((path) => !missing.includes(path));
52991
53101
  const asked = projectScripts.searchedAsTold(present);
@@ -53077,10 +53187,10 @@ ${platform}`;
53077
53187
  const ignored = readStringArray(args, "ignore") ?? [];
53078
53188
  const declaredUnder = ignored.length === 0 ? [] : asked.flatMap((path) => path.endsWith(".gd") ? projectScripts.suite(path) ?? [] : projectScripts.suitesUnder([path]));
53079
53189
  const userData = scratchDirectory("tests");
53080
- const suitesFile = join43(userData, "suites.txt");
53081
- const skippingFile = join43(userData, "skipping.txt");
53082
- const engineLog = join43(userData, "engine.log");
53083
- const expectedFile = join43(userData, "expected.json");
53190
+ const suitesFile = join44(userData, "suites.txt");
53191
+ const skippingFile = join44(userData, "skipping.txt");
53192
+ const engineLog = join44(userData, "engine.log");
53193
+ const expectedFile = join44(userData, "expected.json");
53084
53194
  writeFileSync22(suitesFile, `${(selection.value?.suites ?? asked).join(`
53085
53195
  `)}
53086
53196
  `);
@@ -53121,11 +53231,11 @@ ${platform}`;
53121
53231
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? TEST_TIMEOUT_MS;
53122
53232
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
53123
53233
  const helperFiles = {
53124
- spec: join43(userData, "spec"),
53125
- pid: join43(userData, "pid"),
53126
- error: join43(userData, "error"),
53127
- stop: join43(userData, "stop"),
53128
- step: join43(userData, "step")
53234
+ spec: join44(userData, "spec"),
53235
+ pid: join44(userData, "pid"),
53236
+ error: join44(userData, "error"),
53237
+ stop: join44(userData, "stop"),
53238
+ step: join44(userData, "step")
53129
53239
  };
53130
53240
  const onDesktop = windowed && process.platform === "win32";
53131
53241
  const run = await this.spawnGame(engine.value, cmdArgs, userDataIn(userData), onDesktop ? { directory: project.value.path, files: helperFiles } : undefined);
@@ -53179,7 +53289,7 @@ ${platform}`;
53179
53289
  await run.settled;
53180
53290
  const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync34(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
53181
53291
  if (helperFailed !== null) {
53182
- discard(join43(project.value.path, ".godot", ...ours.split("/")), userData);
53292
+ discard(join44(project.value.path, ".godot", ...ours.split("/")), userData);
53183
53293
  return this.createErrorResponse(`No tests ran: ${named(needing)} ${needing.length === 1 ? "needs" : "need"} a window, and the engine could not be started on a desktop of its own: ${helperFailed}`);
53184
53294
  }
53185
53295
  const endedAt = Date.now();
@@ -53190,7 +53300,7 @@ ${platform}`;
53190
53300
  engine: endedAt - importedAt
53191
53301
  };
53192
53302
  const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
53193
- const reportsDir = join43(project.value.path, ".godot", ...ours.split("/"));
53303
+ const reportsDir = join44(project.value.path, ".godot", ...ours.split("/"));
53194
53304
  let report = null;
53195
53305
  let reportProblem = null;
53196
53306
  const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync37(engineLog, "utf8") : ""), run.log.all);
@@ -53199,7 +53309,7 @@ ${platform}`;
53199
53309
  const written = existsSync34(reportsDir) && notCompiled === null ? readdirSync19(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
53200
53310
  const newest = written.at(-1);
53201
53311
  if (newest !== undefined) {
53202
- report = parseJUnit(readFileSync37(join43(reportsDir, newest, "results.xml"), "utf8"));
53312
+ report = parseJUnit(readFileSync37(join44(reportsDir, newest, "results.xml"), "utf8"));
53203
53313
  }
53204
53314
  } catch (error) {
53205
53315
  reportProblem = errorMessage(error);
@@ -53604,7 +53714,7 @@ ${error.text}`;
53604
53714
  const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
53605
53715
  const wanted = `res://${relativePath}`;
53606
53716
  const folder = dirname25(relativePath.replaceAll("\\", "/"));
53607
- const inFolder = join43(projectPath, folder);
53717
+ const inFolder = join44(projectPath, folder);
53608
53718
  const beside = existsSync34(inFolder) ? readdirSync19(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
53609
53719
  return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts, beside));
53610
53720
  }
@@ -53989,7 +54099,7 @@ ${error.text}`;
53989
54099
  return { ...game, keeper: game.keeper };
53990
54100
  }
53991
54101
  async askTheKeptGame(keeper, request, projectPath) {
53992
- const answered = await askTheKeepersDebugger(keeper, request, KEEPER_DEBUG_MS);
54102
+ const answered = await askTheKeepersDebugger(keeper, request, request.op === "evaluate" ? EVALUATE_MS + 2 * READ_MS : KEEPER_DEBUG_MS);
53993
54103
  if ("unreached" in answered) {
53994
54104
  return this.createErrorResponse(`The debugger of the running game did not answer: ${String(answered.unreached)}.`, [
53995
54105
  "editor_run start starts the game again, with a debugger",
@@ -54005,7 +54115,7 @@ ${error.text}`;
54005
54115
  ...Array.isArray(stack) ? {
54006
54116
  stack: stack.map((frame) => ({
54007
54117
  ...frame,
54008
- ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join43(projectPath, frame["script"].slice("res://".length)) } : {}
54118
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join44(projectPath, frame["script"].slice("res://".length)) } : {}
54009
54119
  }))
54010
54120
  } : {}
54011
54121
  });
@@ -54350,7 +54460,7 @@ ${error.text}`;
54350
54460
  return null;
54351
54461
  }
54352
54462
  try {
54353
- return settingKeys(readFileSync37(join43(projectPath, "project.godot"), "utf8"));
54463
+ return settingKeys(readFileSync37(join44(projectPath, "project.godot"), "utf8"));
54354
54464
  } catch {
54355
54465
  return null;
54356
54466
  }
@@ -54695,7 +54805,7 @@ ${error.text}`;
54695
54805
  if (!paths.ok) {
54696
54806
  return refused(paths.reason, PATH_SOLUTIONS);
54697
54807
  }
54698
- const missing = paths.value.filter((path) => !existsSync34(join43(projectPath, path.slice("res://".length))));
54808
+ const missing = paths.value.filter((path) => !existsSync34(join44(projectPath, path.slice("res://".length))));
54699
54809
  if (missing.length > 0) {
54700
54810
  return refused(`Nothing at ${missing.join(" and ")} in the project at ${projectPath}.`, PATH_SOLUTIONS);
54701
54811
  }
@@ -54866,7 +54976,7 @@ ${error.text}`;
54866
54976
  }
54867
54977
  async runtimeUp(projectPath, before, budget, withArgs) {
54868
54978
  const budgetMs = budget.ms;
54869
- if (!existsSync34(join43(projectPath, RUNTIME_SCRIPT))) {
54979
+ if (!existsSync34(join44(projectPath, RUNTIME_SCRIPT))) {
54870
54980
  return runtimeVerdict(null, {
54871
54981
  addon: false,
54872
54982
  budgetMs,
@@ -55908,7 +56018,7 @@ ${error.text}`;
55908
56018
  };
55909
56019
  const going = await this.runStillGoing(run);
55910
56020
  const projectPath = run.projectPath;
55911
- const projectGone = projectPath !== null && !existsSync34(join43(projectPath, "project.godot"));
56021
+ const projectGone = projectPath !== null && !existsSync34(join44(projectPath, "project.godot"));
55912
56022
  if (projectGone && callersName !== undefined) {
55913
56023
  return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
55914
56024
  }
@@ -56293,7 +56403,7 @@ ${error.text}`;
56293
56403
  notes.push(endedWithoutACode({
56294
56404
  throughEditor: run.throughEditor,
56295
56405
  announced: announced !== undefined || run.announcedPid !== undefined,
56296
- couldAnnounce: run.projectPath !== null && existsSync34(join43(run.projectPath, RUNTIME_SCRIPT)),
56406
+ couldAnnounce: run.projectPath !== null && existsSync34(join44(run.projectPath, RUNTIME_SCRIPT)),
56297
56407
  endedHere: typeof run.endedHere === "string",
56298
56408
  pickedUp: run.pickedUp === true
56299
56409
  }));
@@ -57513,6 +57623,7 @@ ${error.text}`;
57513
57623
  async settleRenameOnDisk(projectPath, changed, asked) {
57514
57624
  const report = {};
57515
57625
  let ok = true;
57626
+ const cacheBefore = asked.rebuildCache ? cachedEntries(projectPath) : null;
57516
57627
  if (asked.moved) {
57517
57628
  const engine = await this.engine();
57518
57629
  const sidecarsBefore = new Set(projectFiles(projectPath, [".uid", ".import"]));
@@ -57527,7 +57638,7 @@ ${error.text}`;
57527
57638
  }
57528
57639
  if (asked.rebuildCache) {
57529
57640
  const rebuilt = await this.rebuildClassCache(projectPath);
57530
- report["classCache"] = rebuilt.ok ? rebuilt.payload : { rebuilt: false, problem: rebuilt.message };
57641
+ report["classCache"] = rebuilt.ok ? { ...rebuilt.payload, ...cacheChangesBetween(cacheBefore, cachedEntries(projectPath)) } : { rebuilt: false, problem: rebuilt.message };
57531
57642
  ok &&= rebuilt.ok;
57532
57643
  }
57533
57644
  if (changed.length > 0) {
@@ -57779,7 +57890,7 @@ ${error.text}`;
57779
57890
  async ownRunOnItsWay(projectPath, budgetMs) {
57780
57891
  const run = await this.currentRun();
57781
57892
  const project = projectPath ?? run?.projectPath ?? null;
57782
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync34(join43(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
57893
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync34(join44(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
57783
57894
  return null;
57784
57895
  }
57785
57896
  const already = runtimesAnnounced().running;
@@ -57889,7 +58000,7 @@ ${error.text}`;
57889
58000
  return this.createErrorResponse(staleNote);
57890
58001
  }
57891
58002
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
57892
- const screenshotPath = screenshotDir ? join43(screenshotDir, "capture.png") : null;
58003
+ const screenshotPath = screenshotDir ? join44(screenshotDir, "capture.png") : null;
57893
58004
  const refused = (message) => this.createErrorResponse(message);
57894
58005
  const onGame = {
57895
58006
  pid: choice.endpoint.pid,
@@ -58093,7 +58204,7 @@ async function runGodotServer() {
58093
58204
  const server = new GodotServer;
58094
58205
  await server.run();
58095
58206
  }
58096
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, SLOW_ANSWER_MS = 1000, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_TIMEOUT_MS = 600000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
58207
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, SLOW_ANSWER_MS = 1000, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_TIMEOUT_MS = 600000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
58097
58208
  var init_server = __esm(() => {
58098
58209
  init_dist2();
58099
58210
  init_stdio();
@@ -58162,7 +58273,6 @@ var init_server = __esm(() => {
58162
58273
  init_update_check();
58163
58274
  __dirname2 = dirname25(fileURLToPath5(import.meta.url));
58164
58275
  KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
58165
- CONSOLE_WRAPPER = /_console(\.exe)?$/i;
58166
58276
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
58167
58277
  VALUE_NAMES = {
58168
58278
  string: "a string",
@@ -58228,7 +58338,7 @@ init_bridge_announce();
58228
58338
  init_engine_launch();
58229
58339
  init_engine_slots();
58230
58340
  import { existsSync as existsSync35 } from "node:fs";
58231
- import { dirname as dirname26, join as join44, resolve as resolve14 } from "node:path";
58341
+ import { dirname as dirname26, join as join45, resolve as resolve14 } from "node:path";
58232
58342
 
58233
58343
  // src/error-mode.ts
58234
58344
  import process8 from "node:process";
@@ -58268,13 +58378,13 @@ init_lsp_client();
58268
58378
  init_process_children();
58269
58379
  import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync16, unlinkSync as unlinkSync2 } from "node:fs";
58270
58380
  import { connect as connect2 } from "node:net";
58271
- import { join as join20, resolve as resolve7 } from "node:path";
58381
+ import { join as join21, resolve as resolve7 } from "node:path";
58272
58382
  var EDITOR_RECORD_PROTOCOL = 1;
58273
58383
  var LSP_WAIT_SECONDS = 20;
58274
58384
  var HOST = "127.0.0.1";
58275
58385
  var LOOK_AGAIN_MS = 500;
58276
58386
  function editorRecordsIn(projectPath) {
58277
- const directory = join20(projectPath, ".godot", "gdharness-editors");
58387
+ const directory = join21(projectPath, ".godot", "gdharness-editors");
58278
58388
  let names;
58279
58389
  try {
58280
58390
  names = readdirSync10(directory).filter((name) => /^\d+\.json$/.test(name));
@@ -58283,7 +58393,7 @@ function editorRecordsIn(projectPath) {
58283
58393
  }
58284
58394
  const records = [];
58285
58395
  for (const name of names) {
58286
- const file = join20(directory, name);
58396
+ const file = join21(directory, name);
58287
58397
  try {
58288
58398
  const said = JSON.parse(readFileSync16(file, "utf8"));
58289
58399
  const { protocol, pid, lsp_port: lspPort, written_at_ms: writtenAtMs } = said;
@@ -58378,7 +58488,7 @@ function lspOptionsOf(args, cwd) {
58378
58488
  }
58379
58489
  }
58380
58490
  const projectPath = resolve7(cwd, named ?? ".");
58381
- if (!existsSync17(join20(projectPath, "project.godot"))) {
58491
+ if (!existsSync17(join21(projectPath, "project.godot"))) {
58382
58492
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
58383
58493
  }
58384
58494
  return { projectPath, port, waitSeconds };
@@ -58452,6 +58562,7 @@ async function pipe(port) {
58452
58562
 
58453
58563
  // src/cli.ts
58454
58564
  init_paths();
58565
+ init_process_children();
58455
58566
 
58456
58567
  // src/prompt.ts
58457
58568
  import process12 from "node:process";
@@ -58485,9 +58596,9 @@ init_setup();
58485
58596
  init_tool_definitions();
58486
58597
  init_tool_reference();
58487
58598
  import { existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync12, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "node:fs";
58488
- import { basename as basename8, dirname as dirname10, join as join22 } from "node:path";
58599
+ import { basename as basename8, dirname as dirname10, join as join23 } from "node:path";
58489
58600
  var SKILL_NAME = "gdharness";
58490
- var SHARED_SKILLS = join22(".agents", "skills");
58601
+ var SHARED_SKILLS = join23(".agents", "skills");
58491
58602
  function skillMarkdown(version) {
58492
58603
  return `---
58493
58604
  name: ${SKILL_NAME}
@@ -58728,27 +58839,27 @@ what they report while starting comes back under \`engine_messages_before_operat
58728
58839
  function skillFiles(version) {
58729
58840
  return new Map([
58730
58841
  ["SKILL.md", skillMarkdown(version)],
58731
- [join22("references", "tools.md"), renderToolsMarkdown()]
58842
+ [join23("references", "tools.md"), renderToolsMarkdown()]
58732
58843
  ]);
58733
58844
  }
58734
58845
  function skillDirectories(harnesses, projectPath, exists) {
58735
- const directories = [join22(projectPath, SHARED_SKILLS, SKILL_NAME)];
58846
+ const directories = [join23(projectPath, SHARED_SKILLS, SKILL_NAME)];
58736
58847
  for (const harness of harnesses) {
58737
58848
  if (harness.skills === undefined) {
58738
58849
  continue;
58739
58850
  }
58740
- const own = join22(projectPath, harness.skills.dir);
58741
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join22(own, SKILL_NAME))) {
58742
- directories.push(join22(own, SKILL_NAME));
58851
+ const own = join23(projectPath, harness.skills.dir);
58852
+ if ((!harness.skills.shared || exists(own)) && !directories.includes(join23(own, SKILL_NAME))) {
58853
+ directories.push(join23(own, SKILL_NAME));
58743
58854
  }
58744
58855
  }
58745
58856
  return directories;
58746
58857
  }
58747
58858
  function everySkillDirectory(projectPath, harnesses) {
58748
- const directories = new Set([join22(projectPath, SHARED_SKILLS, SKILL_NAME)]);
58859
+ const directories = new Set([join23(projectPath, SHARED_SKILLS, SKILL_NAME)]);
58749
58860
  for (const harness of harnesses) {
58750
58861
  if (harness.skills !== undefined) {
58751
- directories.add(join22(projectPath, harness.skills.dir, SKILL_NAME));
58862
+ directories.add(join23(projectPath, harness.skills.dir, SKILL_NAME));
58752
58863
  }
58753
58864
  }
58754
58865
  return [...directories];
@@ -58765,7 +58876,7 @@ function removeSkill(directories, projectPath) {
58765
58876
  return removed;
58766
58877
  }
58767
58878
  function pruneEmpty(directory, projectPath) {
58768
- const shared = join22(projectPath, ".agents");
58879
+ const shared = join23(projectPath, ".agents");
58769
58880
  let path = directory;
58770
58881
  while (path !== projectPath && (basename8(path) === "skills" || path === shared)) {
58771
58882
  if (readdirSync12(path).length > 0) {
@@ -58781,7 +58892,7 @@ function writeSkill(directories, version) {
58781
58892
  const replaced = existsSync19(directory);
58782
58893
  rmSync10(directory, { recursive: true, force: true });
58783
58894
  for (const [name, contents] of files) {
58784
- const path = join22(directory, name);
58895
+ const path = join23(directory, name);
58785
58896
  mkdirSync7(dirname10(path), { recursive: true });
58786
58897
  writeFileSync11(path, contents, "utf8");
58787
58898
  }
@@ -58796,7 +58907,7 @@ init_setup();
58796
58907
  init_suite_judge();
58797
58908
  import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync19, rmSync as rmSync11 } from "node:fs";
58798
58909
  import { availableParallelism as availableParallelism3 } from "node:os";
58799
- import { join as join23, resolve as resolve9 } from "node:path";
58910
+ import { join as join24, resolve as resolve9 } from "node:path";
58800
58911
  import process13 from "node:process";
58801
58912
  var SHARE = "--gdh-share=";
58802
58913
  var GATHER = "--gdh-share-gather=";
@@ -58836,9 +58947,9 @@ function spreadOptionsOf(args, cwd) {
58836
58947
  }
58837
58948
  const first = positional[0];
58838
58949
  const named = first === undefined ? null : resolve9(cwd, first);
58839
- const projectNamed = named !== null && existsSync20(join23(named, "project.godot"));
58950
+ const projectNamed = named !== null && existsSync20(join24(named, "project.godot"));
58840
58951
  const projectPath = projectNamed ? named : resolve9(cwd);
58841
- if (!existsSync20(join23(projectPath, "project.godot"))) {
58952
+ if (!existsSync20(join24(projectPath, "project.godot"))) {
58842
58953
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
58843
58954
  }
58844
58955
  const scenes = positional.slice(projectNamed ? 1 : 0);
@@ -58846,7 +58957,7 @@ function spreadOptionsOf(args, cwd) {
58846
58957
  throw new EngineUsageError(scenes.length === 0 ? "gdharness spread needs the scene of the bench to play." : `gdharness spread plays one scene, and was given ${String(scenes.length)}: ${scenes.join(", ")}.`);
58847
58958
  }
58848
58959
  const scene = resPathOf(scenes[0] ?? "", projectPath, cwd);
58849
- if (!existsSync20(join23(projectPath, scene.slice("res://".length)))) {
58960
+ if (!existsSync20(join24(projectPath, scene.slice("res://".length)))) {
58850
58961
  throw new EngineUsageError(`Nothing at ${scene} in the project at ${projectPath}.`);
58851
58962
  }
58852
58963
  const benchArguments = dashes === -1 ? [] : args.slice(dashes + 1);
@@ -58917,14 +59028,14 @@ function shareFaults(played) {
58917
59028
  return faults;
58918
59029
  }
58919
59030
  async function runSpreadCommand(options, godotPath) {
58920
- if (!existsSync20(join23(options.projectPath, SHARE_SCRIPT))) {
59031
+ if (!existsSync20(join24(options.projectPath, SHARE_SCRIPT))) {
58921
59032
  throw new EngineUsageError(`res://${SHARE_SCRIPT} is not in the project, so its bench cannot ask GdhShare for outcomes. Run gdharness setup.`);
58922
59033
  }
58923
59034
  const startedAt = Date.now();
58924
- const spreadRuns = join23(options.projectPath, ".godot", "gdharness", "spread-runs");
59035
+ const spreadRuns = join24(options.projectPath, ".godot", "gdharness", "spread-runs");
58925
59036
  sweepRunDirectories(spreadRuns);
58926
- const runDirectory = join23(spreadRuns, `${String(startedAt)}-${String(process13.pid)}`);
58927
- const shareDirectory = join23(runDirectory, "share");
59037
+ const runDirectory = join24(spreadRuns, `${String(startedAt)}-${String(process13.pid)}`);
59038
+ const shareDirectory = join24(runDirectory, "share");
58928
59039
  mkdirSync8(shareDirectory, { recursive: true });
58929
59040
  const chosen = machineSlots({ directory: options.slotsDirectory, count: options.slotCount });
58930
59041
  const slotCount = chosen.count;
@@ -58963,7 +59074,7 @@ async function runSpreadCommand(options, godotPath) {
58963
59074
  share,
58964
59075
  `${INTO}${shareDirectory}`
58965
59076
  ]);
58966
- const logs = Array.from({ length: workers }, (_, index) => join23(runDirectory, `worker-${String(index)}`, "engine.log"));
59077
+ const logs = Array.from({ length: workers }, (_, index) => join24(runDirectory, `worker-${String(index)}`, "engine.log"));
58967
59078
  let done = 0;
58968
59079
  const heartbeat = setInterval(() => {
58969
59080
  console.log(`[spread] ${progressOf(logs)}`);
@@ -58981,7 +59092,7 @@ async function runSpreadCommand(options, godotPath) {
58981
59092
  }
58982
59093
  const faults = left.flatMap((worker) => engineFaults(worker, options.godotVersion));
58983
59094
  const arms = left.map((_, index) => {
58984
- const file = join23(shareDirectory, `worker-${String(index)}.arms`);
59095
+ const file = join24(shareDirectory, `worker-${String(index)}.arms`);
58985
59096
  return existsSync20(file) ? armsPlayed(readFileSync19(file, "utf8")) : [];
58986
59097
  });
58987
59098
  faults.push(...shareFaults(arms));
@@ -58991,7 +59102,7 @@ async function runSpreadCommand(options, godotPath) {
58991
59102
  const gather = await engine("gather", `${GATHER}${String(workers)}`);
58992
59103
  console.log(readFileSync19(gather.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd());
58993
59104
  const gatherFaults = engineFaults(gather, options.godotVersion);
58994
- const gatheredFile = join23(shareDirectory, "gathered.arms");
59105
+ const gatheredFile = join24(shareDirectory, "gathered.arms");
58995
59106
  const gathered = new Set((existsSync20(gatheredFile) ? armsPlayed(readFileSync19(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
58996
59107
  const ungathered = (arms[0] ?? []).filter((arm) => !gathered.has(arm.arm)).map((arm) => arm.arm);
58997
59108
  if (ungathered.length > 0) {
@@ -59012,9 +59123,9 @@ async function runSpreadCommand(options, godotPath) {
59012
59123
  }
59013
59124
  }
59014
59125
  async function played(launch, slots, options, runDirectory, name, benchArguments) {
59015
- const directory = join23(runDirectory, name);
59016
- mkdirSync8(join23(directory, "user"), { recursive: true });
59017
- const log = join23(directory, "engine.log");
59126
+ const directory = join24(runDirectory, name);
59127
+ mkdirSync8(join24(directory, "user"), { recursive: true });
59128
+ const log = join24(directory, "engine.log");
59018
59129
  const args = [
59019
59130
  "--headless",
59020
59131
  "--audio-driver",
@@ -59097,14 +59208,14 @@ import {
59097
59208
  writeFileSync as writeFileSync12
59098
59209
  } from "node:fs";
59099
59210
  import { availableParallelism as availableParallelism4 } from "node:os";
59100
- import { dirname as dirname13, join as join27, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
59211
+ import { dirname as dirname13, join as join28, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
59101
59212
  import process14 from "node:process";
59102
59213
 
59103
59214
  // src/suite-fingerprint.ts
59104
59215
  init_class_cache();
59105
59216
  import { createHash as createHash6 } from "node:crypto";
59106
59217
  import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync20, statSync as statSync13 } from "node:fs";
59107
- import { join as join24, posix as posix2 } from "node:path";
59218
+ import { join as join25, posix as posix2 } from "node:path";
59108
59219
  var RES_LITERAL = /res:\/\/[^"'\s)\]<>]*/g;
59109
59220
  var RELATIVE_LOAD = /\b(?:pre)?load\(\s*["']((?!res:\/\/|uid:\/\/)[^"']+)["']/g;
59110
59221
  var REACHES_AT_RUN_TIME = [
@@ -59163,7 +59274,7 @@ ${this.read(path).digest}
59163
59274
  const hash = createHash6("sha256");
59164
59275
  const walk = (directory, under) => {
59165
59276
  for (const entry of readdirSync13(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
59166
- const file = join24(directory, entry.name);
59277
+ const file = join25(directory, entry.name);
59167
59278
  if (entry.isDirectory()) {
59168
59279
  walk(file, `${under}${entry.name}/`);
59169
59280
  } else if (entry.isFile()) {
@@ -59172,7 +59283,7 @@ ${this.read(path).digest}
59172
59283
  }
59173
59284
  }
59174
59285
  };
59175
- const directory = join24(this.scripts.projectPath, root.slice("res://".length));
59286
+ const directory = join25(this.scripts.projectPath, root.slice("res://".length));
59176
59287
  if (existsSync21(directory)) {
59177
59288
  walk(directory, "");
59178
59289
  }
@@ -59182,7 +59293,7 @@ ${this.read(path).digest}
59182
59293
  if (PLACEHOLDER.test(path)) {
59183
59294
  return { digest: "", reaches: [], unreadable: `${path} is filled in at run time` };
59184
59295
  }
59185
- const file = join24(this.scripts.projectPath, path.slice("res://".length));
59296
+ const file = join25(this.scripts.projectPath, path.slice("res://".length));
59186
59297
  if (!existsSync21(file)) {
59187
59298
  return { digest: "missing", reaches: [], unreadable: null };
59188
59299
  }
@@ -59231,7 +59342,7 @@ ${this.read(path).digest}
59231
59342
  }
59232
59343
  }
59233
59344
  function extensionsOf(projectPath) {
59234
- const list = join24(projectPath, ".godot", "extension_list.cfg");
59345
+ const list = join25(projectPath, ".godot", "extension_list.cfg");
59235
59346
  return existsSync21(list) ? readFileSync20(list, "utf8").split(`
59236
59347
  `).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
59237
59348
  }
@@ -59242,13 +59353,13 @@ init_suite_plan();
59242
59353
 
59243
59354
  // src/version.ts
59244
59355
  import { readFileSync as readFileSync22 } from "node:fs";
59245
- import { dirname as dirname12, join as join26 } from "node:path";
59356
+ import { dirname as dirname12, join as join27 } from "node:path";
59246
59357
  import { fileURLToPath as fileURLToPath3 } from "node:url";
59247
59358
  function getLocalVersion() {
59248
59359
  const currentDirectory = dirname12(fileURLToPath3(import.meta.url));
59249
59360
  const packageCandidates = [
59250
- join26(currentDirectory, "..", "package.json"),
59251
- join26(currentDirectory, "..", "..", "package.json")
59361
+ join27(currentDirectory, "..", "package.json"),
59362
+ join27(currentDirectory, "..", "..", "package.json")
59252
59363
  ];
59253
59364
  for (const packagePath of packageCandidates) {
59254
59365
  try {
@@ -59319,9 +59430,9 @@ function testOptionsOf(args, cwd) {
59319
59430
  }
59320
59431
  const first = positional[0];
59321
59432
  const named = first === undefined ? null : resolve10(cwd, first);
59322
- const projectNamed = named !== null && existsSync23(join27(named, "project.godot"));
59433
+ const projectNamed = named !== null && existsSync23(join28(named, "project.godot"));
59323
59434
  const projectPath = projectNamed ? named : resolve10(cwd);
59324
- if (!existsSync23(join27(projectPath, "project.godot"))) {
59435
+ if (!existsSync23(join28(projectPath, "project.godot"))) {
59325
59436
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
59326
59437
  }
59327
59438
  const last = (flag) => valued.get(flag)?.at(-1);
@@ -59355,11 +59466,11 @@ function seedOf(value) {
59355
59466
  return seed;
59356
59467
  }
59357
59468
  function rootsOf(options) {
59358
- return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync23(join27(options.projectPath, root))).map((root) => `res://${root}`);
59469
+ return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync23(join28(options.projectPath, root))).map((root) => `res://${root}`);
59359
59470
  }
59360
59471
  function searchedRoots(options) {
59361
59472
  return options.selected.length === 0 ? rootsOf(options) : options.selected.filter((entry) => {
59362
- const directory = join27(options.projectPath, entry.slice("res://".length));
59473
+ const directory = join28(options.projectPath, entry.slice("res://".length));
59363
59474
  return existsSync23(directory) && statSync14(directory).isDirectory();
59364
59475
  });
59365
59476
  }
@@ -59376,7 +59487,7 @@ function selectedSuites(scripts, options) {
59376
59487
  for (const entry of options.selected) {
59377
59488
  const caseAt = /^(res:\/\/.*\.gd):(\w+)$/.exec(entry);
59378
59489
  const path = caseAt?.[1] ?? entry;
59379
- const directory = join27(options.projectPath, path.slice(6));
59490
+ const directory = join28(options.projectPath, path.slice(6));
59380
59491
  const suites = existsSync23(directory) && statSync14(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
59381
59492
  if (suites.length === 0) {
59382
59493
  throw new EngineUsageError(existsSync23(directory) ? `${path} holds no gdUnit4 suite: nothing there extends GdUnitTestSuite and declares a test_ function.` : `Nothing at ${path} in the project at ${options.projectPath}.`);
@@ -59433,7 +59544,7 @@ class RunLogs {
59433
59544
  return this.kept ?? this.directory;
59434
59545
  }
59435
59546
  named(path) {
59436
- return this.kept === null ? path : join27(this.kept, relative5(this.directory, path));
59547
+ return this.kept === null ? path : join28(this.kept, relative5(this.directory, path));
59437
59548
  }
59438
59549
  keep() {
59439
59550
  if (this.kept === null) {
@@ -59441,8 +59552,8 @@ class RunLogs {
59441
59552
  }
59442
59553
  for (const file of readdirSync14(this.directory, { recursive: true, encoding: "utf8" })) {
59443
59554
  if (file.endsWith(".log") && file.split(sep4).length === 2) {
59444
- mkdirSync10(dirname13(join27(this.kept, file)), { recursive: true });
59445
- copyFileSync(join27(this.directory, file), join27(this.kept, file));
59555
+ mkdirSync10(dirname13(join28(this.kept, file)), { recursive: true });
59556
+ copyFileSync(join28(this.directory, file), join28(this.kept, file));
59446
59557
  }
59447
59558
  }
59448
59559
  }
@@ -59455,24 +59566,24 @@ async function runTestCommand(options, godotPath) {
59455
59566
  const startedAt = Date.now();
59456
59567
  const scripts = new ProjectScripts(options.projectPath);
59457
59568
  const home = homeOf(options.projectPath);
59458
- const state = join27(home, ".godot", "gdharness");
59459
- const timesFile = join27(state, "suite-times.json");
59460
- const passedFile = join27(state, "suite-passed.json");
59461
- const lastRunFile = join27(state, LAST_RUN_FILE);
59569
+ const state = join28(home, ".godot", "gdharness");
59570
+ const timesFile = join28(state, "suite-times.json");
59571
+ const passedFile = join28(state, "suite-passed.json");
59572
+ const lastRunFile = join28(state, LAST_RUN_FILE);
59462
59573
  let suites = selectedSuites(scripts, options);
59463
59574
  const uncollected = uncollectedSaid(scripts.uncollectedUnder(searchedRoots(options)));
59464
59575
  const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
59465
59576
  const declared = new Map(wholeSuites.map((suite) => [suite.path, suite.cases]));
59466
- const testRuns = join27(options.projectPath, ".godot", "gdharness", "test-runs");
59467
- const keptRuns = join27(state, "test-runs");
59577
+ const testRuns = join28(options.projectPath, ".godot", "gdharness", "test-runs");
59578
+ const keptRuns = join28(state, "test-runs");
59468
59579
  sweepRunDirectories(testRuns);
59469
59580
  if (keptRuns !== testRuns) {
59470
59581
  sweepRunDirectories(keptRuns);
59471
59582
  }
59472
59583
  const runName = `${String(startedAt)}-${String(process14.pid)}`;
59473
- const runDirectory = join27(testRuns, runName);
59584
+ const runDirectory = join28(testRuns, runName);
59474
59585
  mkdirSync10(runDirectory, { recursive: true });
59475
- const logs = new RunLogs(runDirectory, keptRuns === testRuns ? null : join27(keptRuns, runName));
59586
+ const logs = new RunLogs(runDirectory, keptRuns === testRuns ? null : join28(keptRuns, runName));
59476
59587
  const chosen = machineSlots({ directory: options.slotsDirectory, count: options.slotCount });
59477
59588
  const slots = new EngineSlots(chosen.directory, chosen.count, { pid: process14.pid, started: await ownStart(), by: `gdharness test of ${options.projectPath}` }, (line) => {
59478
59589
  console.log(line);
@@ -59680,9 +59791,9 @@ async function importProject(launch) {
59680
59791
  console.log("The import was skipped: every imported file and the class cache are current.");
59681
59792
  return null;
59682
59793
  }
59683
- const directory = join27(launch.runDirectory, "import");
59684
- mkdirSync10(join27(directory, "user"), { recursive: true });
59685
- const log = join27(directory, "import.log");
59794
+ const directory = join28(launch.runDirectory, "import");
59795
+ mkdirSync10(join28(directory, "user"), { recursive: true });
59796
+ const log = join28(directory, "import.log");
59686
59797
  const lock = await launch.slots.take();
59687
59798
  try {
59688
59799
  freeLibraryCopies(launch.projectPath);
@@ -59747,12 +59858,12 @@ async function runShares(launch, units, declared, lanes, taken, times) {
59747
59858
  return (await Promise.all(Array.from({ length: lanes }, (_, at) => lane(at)))).flat().sort((a, b) => a.index - b.index);
59748
59859
  }
59749
59860
  async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59750
- const directory = join27(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
59751
- mkdirSync10(join27(directory, "user"), { recursive: true });
59752
- const suitesFile = join27(directory, "suites.txt");
59753
- const skippingFile = join27(directory, "skipping.txt");
59754
- const rowsFile = join27(directory, "rows.txt");
59755
- const expectedFile = join27(directory, "expected.json");
59861
+ const directory = join28(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
59862
+ mkdirSync10(join28(directory, "user"), { recursive: true });
59863
+ const suitesFile = join28(directory, "suites.txt");
59864
+ const skippingFile = join28(directory, "skipping.txt");
59865
+ const rowsFile = join28(directory, "rows.txt");
59866
+ const expectedFile = join28(directory, "expected.json");
59756
59867
  writeFileSync12(suitesFile, `${plan.suites.map((suite) => suite.path).join(`
59757
59868
  `)}
59758
59869
  `);
@@ -59779,7 +59890,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59779
59890
  ...plan.windowed ? [] : ["--ignoreHeadlessMode"],
59780
59891
  "-c",
59781
59892
  "-rd",
59782
- `res://${relative5(launch.projectPath, join27(directory, "reports")).split(sep4).join("/")}`,
59893
+ `res://${relative5(launch.projectPath, join28(directory, "reports")).split(sep4).join("/")}`,
59783
59894
  "-rc",
59784
59895
  "1",
59785
59896
  "--",
@@ -59795,7 +59906,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59795
59906
  "--expected-out",
59796
59907
  expectedFile
59797
59908
  ];
59798
- const log = join27(directory, "engine.log");
59909
+ const log = join28(directory, "engine.log");
59799
59910
  const lock = slot ?? await launch.slots.take();
59800
59911
  const began = Date.now();
59801
59912
  let end;
@@ -59814,7 +59925,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59814
59925
  godotVersion: launch.godotVersion
59815
59926
  };
59816
59927
  const judged = judgeEngine(printed, assignment, end);
59817
- const written = reportsFault(join27(directory, "reports"));
59928
+ const written = reportsFault(join28(directory, "reports"));
59818
59929
  return {
59819
59930
  index,
59820
59931
  plan,
@@ -59828,7 +59939,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59828
59939
  }
59829
59940
  var REPORT_FILES_NAMED = 3;
59830
59941
  function reportsFault(reports) {
59831
- const files = existsSync23(reports) ? readdirSync14(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join27(reports, file)).isFile()).sort() : [];
59942
+ const files = existsSync23(reports) ? readdirSync14(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join28(reports, file)).isFile()).sort() : [];
59832
59943
  if (files.length === 0) {
59833
59944
  return null;
59834
59945
  }
@@ -59986,7 +60097,7 @@ class UsageError extends Refusal {
59986
60097
  function projectArgument(at) {
59987
60098
  const named = args.slice(at).find((value) => !value.startsWith("--"));
59988
60099
  const projectPath = resolve14(named ?? ".");
59989
- if (!existsSync35(join44(projectPath, "project.godot"))) {
60100
+ if (!existsSync35(join45(projectPath, "project.godot"))) {
59990
60101
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
59991
60102
  }
59992
60103
  return projectPath;
@@ -60050,7 +60161,7 @@ async function registerRuntime(godot, projectPath) {
60050
60161
  }
60051
60162
  const path = (named ?? "").replace(/^res:\/\//, "");
60052
60163
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
60053
- if (!existsSync35(join44(projectPath, path))) {
60164
+ if (!existsSync35(join45(projectPath, path))) {
60054
60165
  console.log(" that file is not in this project, so it boots with a missing script");
60055
60166
  }
60056
60167
  console.log(` ${useTheShippedLoader(RUNTIME_AUTOLOAD.name, path)}`);
@@ -60308,10 +60419,8 @@ async function upgrade() {
60308
60419
  }
60309
60420
  console.log(`
60310
60421
  On ${version}. What is left for you:`);
60311
- console.log(digestBefore !== null && digestBefore === installedEditorDigest(projectPath) ? ` 1. Nothing for the editor: this upgrade left the editor addons the same code as before,
60312
- ` + ` so it gives an open editor nothing to restart for. editor_status says addonIsStale
60313
- ` + " if the editor is running other code." : ` 1. The open editor is still running the addons it loaded at startup. Restart it with the
60314
- ` + " editor_launch restart tool, which closes and reopens the window.");
60422
+ const tree = await processTree();
60423
+ console.log(editorUpgradeNote(digestBefore !== null && digestBefore === installedEditorDigest(projectPath), tree === undefined ? null : editorsOfProjectIn(tree, projectPath)));
60315
60424
  const serving = await serverServing(projectPath);
60316
60425
  console.log(harnessNote({ moved, written, byHand }, [launch.command, ...launch.args].join(" "), serving === null ? null : { server: serving, isThisVersion: serving.version === version }));
60317
60426
  const trailing = worktreeCheckoutsOf(projectPath).flatMap((path) => {