gdharness 1.2.37 → 1.2.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -3365,18 +3365,18 @@ import {
3365
3365
  closeSync as closeSync3,
3366
3366
  constants,
3367
3367
  copyFileSync,
3368
- existsSync as existsSync20,
3368
+ existsSync as existsSync21,
3369
3369
  fstatSync,
3370
3370
  mkdirSync as mkdirSync15,
3371
3371
  openSync as openSync4,
3372
- readdirSync as readdirSync11,
3372
+ readdirSync as readdirSync12,
3373
3373
  readFileSync as readFileSync24,
3374
3374
  readSync as readSync2,
3375
3375
  rmdirSync as rmdirSync2,
3376
- statSync as statSync12,
3376
+ statSync as statSync13,
3377
3377
  writeFileSync as writeFileSync16
3378
3378
  } from "node:fs";
3379
- import { basename as basename7, dirname as dirname18, extname as extname4, isAbsolute as isAbsolute4, join as join26, normalize as normalize2, relative as relative5 } from "node:path";
3379
+ import { basename as basename8, dirname as dirname19, extname as extname4, isAbsolute as isAbsolute5, join as join27, normalize as normalize2, relative as relative5 } from "node:path";
3380
3380
  import { setTimeout as delay5 } from "node:timers/promises";
3381
3381
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3382
3382
 
@@ -28538,19 +28538,11 @@ function dictionary(entries) {
28538
28538
  return Object.assign(emptyRecord(), entries);
28539
28539
  }
28540
28540
 
28541
- // src/editor-log.ts
28542
- import {
28543
- closeSync as closeSync2,
28544
- existsSync as existsSync7,
28545
- mkdirSync as mkdirSync7,
28546
- openSync as openSync2,
28547
- readFileSync as readFileSync12,
28548
- readSync,
28549
- rmSync as rmSync6,
28550
- statSync as statSync5,
28551
- writeFileSync as writeFileSync8
28552
- } from "node:fs";
28553
- import { dirname as dirname7, join as join11 } from "node:path";
28541
+ // src/doc-cache.ts
28542
+ import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5 } from "node:fs";
28543
+ import { homedir as homedir2 } from "node:os";
28544
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute3, join as join11 } from "node:path";
28545
+ import process7 from "node:process";
28554
28546
 
28555
28547
  // src/game-log.ts
28556
28548
  import { StringDecoder } from "node:string_decoder";
@@ -28758,6 +28750,9 @@ class GameLog {
28758
28750
  count(severity) {
28759
28751
  return this.entries.filter((entry) => entry.severity === severity).length;
28760
28752
  }
28753
+ crashReport() {
28754
+ return this.entries.find((entry) => CRASH.test(entry.text));
28755
+ }
28761
28756
  everything() {
28762
28757
  return this.entries;
28763
28758
  }
@@ -28791,12 +28786,131 @@ function answersTo(entry, severity, contains) {
28791
28786
  return [entry.text, ...entry.detail].some((line) => line.toLowerCase().includes(needle));
28792
28787
  }
28793
28788
 
28789
+ // src/engine-run.ts
28790
+ function runEngine(executable, args, options = {}) {
28791
+ return new Promise((resolve) => {
28792
+ const log = new GameLog;
28793
+ let failure = null;
28794
+ const signal = callSignal();
28795
+ const child = spawnHidden(executable, args, {
28796
+ stdio: ["ignore", "pipe", "pipe"],
28797
+ ...signal === undefined ? {} : { signal }
28798
+ });
28799
+ child.stdout.on("data", (chunk) => {
28800
+ log.append("stdout", chunk);
28801
+ });
28802
+ child.stderr.on("data", (chunk) => {
28803
+ log.append("stderr", chunk);
28804
+ });
28805
+ const { timeout } = options;
28806
+ const timer = timeout === undefined ? undefined : setTimeout(() => {
28807
+ failure ??= `it ran past its ${timeout.said}`;
28808
+ child.kill();
28809
+ }, timeout.ms);
28810
+ child.on("error", (error) => {
28811
+ failure ??= error.name === "AbortError" ? "the call was cancelled" : `it could not be started: ${error.message}`;
28812
+ });
28813
+ child.on("close", (code, closedBy) => {
28814
+ clearTimeout(timer);
28815
+ log.finish();
28816
+ const started = child.pid !== undefined;
28817
+ resolve({ log, exitCode: started ? code : null, exitSignal: closedBy, failure });
28818
+ });
28819
+ });
28820
+ }
28821
+ var WINDOWS_STATUSES = new Map([
28822
+ [3221225477, "an access violation, so it crashed"],
28823
+ [3221225501, "an illegal instruction, so it crashed"],
28824
+ [3221225620, "an integer division by zero, so it crashed"],
28825
+ [3221225725, "a stack overflow, so it crashed"],
28826
+ [3221225781, "a library it needs was not found, so it never started"],
28827
+ [3221225786, "its console was closed or sent Ctrl+C"],
28828
+ [3221226356, "a corrupted heap, so it crashed"],
28829
+ [3221226505, "a stack buffer overrun or a fast fail, so it crashed"]
28830
+ ]);
28831
+ function exitCodeText(code) {
28832
+ if (code === null) {
28833
+ return "exit code unknown";
28834
+ }
28835
+ if (code < 3221225472) {
28836
+ return `exit code ${code}`;
28837
+ }
28838
+ const hex = `0x${code.toString(16).toUpperCase()}`;
28839
+ return `exit code ${hex}, ${WINDOWS_STATUSES.get(code) ?? "a Windows error status"}`;
28840
+ }
28841
+ function howItEnded(run) {
28842
+ if (run.failure !== null) {
28843
+ return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
28844
+ }
28845
+ return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}`;
28846
+ }
28847
+
28848
+ // src/doc-cache.ts
28849
+ var CACHE_FILE = /^editor_doc_cache-\d+\.\d+\.res$/;
28850
+ function editorCacheDirectory(godotPath, platform = process7.platform, env = process7.env, home = homedir2()) {
28851
+ const beside = dirname7(godotPath);
28852
+ const bundle = platform === "darwin" && basename2(beside) === "MacOS" && basename2(dirname7(beside)) === "Contents" ? dirname7(dirname7(dirname7(beside))) : null;
28853
+ for (const folder of bundle === null ? [beside] : [beside, bundle]) {
28854
+ if (existsSync7(join11(folder, "._sc_")) || existsSync7(join11(folder, "_sc_"))) {
28855
+ return join11(folder, "editor_data", "cache");
28856
+ }
28857
+ }
28858
+ if (platform === "win32") {
28859
+ const set = (name) => env[name] === "" ? undefined : env[name];
28860
+ return join11(set("LOCALAPPDATA") ?? set("TEMP") ?? join11(home, "AppData", "Local"), "Godot");
28861
+ }
28862
+ if (platform === "darwin") {
28863
+ return join11(home, "Library", "Caches", "Godot");
28864
+ }
28865
+ const xdg = env["XDG_CACHE_HOME"];
28866
+ return join11(xdg !== undefined && isAbsolute3(xdg) ? xdg : join11(home, ".cache"), "godot");
28867
+ }
28868
+ function docCachesWritten(directory) {
28869
+ const written = new Map;
28870
+ let names;
28871
+ try {
28872
+ names = readdirSync3(directory);
28873
+ } catch {
28874
+ return written;
28875
+ }
28876
+ for (const name of names.filter((one) => CACHE_FILE.test(one))) {
28877
+ try {
28878
+ written.set(join11(directory, name), statSync5(join11(directory, name)).mtimeMs);
28879
+ } catch {}
28880
+ }
28881
+ return written;
28882
+ }
28883
+ function docCacheCrash(ran, before, after, what) {
28884
+ const report = ran.log.crashReport();
28885
+ if (ran.exitCode === 0 && ran.exitSignal === null && report === undefined || ran.failure !== null) {
28886
+ return null;
28887
+ }
28888
+ const rewritten = [...after].find(([path, at]) => before.get(path) !== at)?.[0];
28889
+ if (rewritten === undefined) {
28890
+ return null;
28891
+ }
28892
+ const said = report === undefined ? "" : `, and its crash handler printed "${report.text}"`;
28893
+ return `The first ${what} ended badly (${howItEnded(ran)}${said}) after rebuilding the editor's class reference ` + `cache, ${rewritten}. Godot crashes as an import or an export exits when it finds that cache ` + "stale, which it is whenever another build of the same minor version wrote it last, such as 4.7.1 " + `beside 4.7.2, and the crashing run writes a fresh one. So the ${what} was run again, and this answer ` + "is from that second run.";
28894
+ }
28895
+
28794
28896
  // src/editor-log.ts
28897
+ import {
28898
+ closeSync as closeSync2,
28899
+ existsSync as existsSync8,
28900
+ mkdirSync as mkdirSync7,
28901
+ openSync as openSync2,
28902
+ readFileSync as readFileSync12,
28903
+ readSync,
28904
+ rmSync as rmSync6,
28905
+ statSync as statSync6,
28906
+ writeFileSync as writeFileSync8
28907
+ } from "node:fs";
28908
+ import { dirname as dirname8, join as join12 } from "node:path";
28795
28909
  function editorLogPath(projectPath) {
28796
- return join11(projectPath, ".godot", "gdharness-editor.log");
28910
+ return join12(projectPath, ".godot", "gdharness-editor.log");
28797
28911
  }
28798
28912
  function editorLogNotePath(projectPath) {
28799
- return join11(projectPath, ".godot", "gdharness-editor.json");
28913
+ return join12(projectPath, ".godot", "gdharness-editor.json");
28800
28914
  }
28801
28915
  function readEditorLogNote(projectPath) {
28802
28916
  try {
@@ -28811,7 +28925,7 @@ function readEditorLogNote(projectPath) {
28811
28925
  function clearEditorLog(projectPath) {
28812
28926
  try {
28813
28927
  const path = editorLogPath(projectPath);
28814
- mkdirSync7(dirname7(path), { recursive: true });
28928
+ mkdirSync7(dirname8(path), { recursive: true });
28815
28929
  rmSync6(path, { force: true });
28816
28930
  rmSync6(editorLogNotePath(projectPath), { force: true });
28817
28931
  } catch {}
@@ -28819,7 +28933,7 @@ function clearEditorLog(projectPath) {
28819
28933
  function writeEditorLogNote(projectPath, pid) {
28820
28934
  try {
28821
28935
  const path = editorLogNotePath(projectPath);
28822
- mkdirSync7(dirname7(path), { recursive: true });
28936
+ mkdirSync7(dirname8(path), { recursive: true });
28823
28937
  writeFileSync8(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
28824
28938
  } catch {}
28825
28939
  }
@@ -28841,7 +28955,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
28841
28955
  return { kind: "older server", notedPid: noted?.pid ?? null };
28842
28956
  }
28843
28957
  const path = editorLogPath(projectPath);
28844
- if (!existsSync7(path)) {
28958
+ if (!existsSync8(path)) {
28845
28959
  return { kind: "not written" };
28846
28960
  }
28847
28961
  const log = new GameLog;
@@ -28900,7 +29014,7 @@ function foldProgress(text) {
28900
29014
  }
28901
29015
  var MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
28902
29016
  function theFirstOf(path) {
28903
- const whole = statSync5(path).size;
29017
+ const whole = statSync6(path).size;
28904
29018
  if (whole <= MOST_CONSOLE_BYTES) {
28905
29019
  return { text: readFileSync12(path, "utf8") };
28906
29020
  }
@@ -29057,60 +29171,21 @@ function linesBefore(entries, at, collapsed, wanted) {
29057
29171
  return found.reverse();
29058
29172
  }
29059
29173
 
29060
- // src/engine-run.ts
29061
- function runEngine(executable, args, options = {}) {
29062
- return new Promise((resolve) => {
29063
- const log = new GameLog;
29064
- let failure = null;
29065
- const signal = callSignal();
29066
- const child = spawnHidden(executable, args, {
29067
- stdio: ["ignore", "pipe", "pipe"],
29068
- ...signal === undefined ? {} : { signal }
29069
- });
29070
- child.stdout.on("data", (chunk) => {
29071
- log.append("stdout", chunk);
29072
- });
29073
- child.stderr.on("data", (chunk) => {
29074
- log.append("stderr", chunk);
29075
- });
29076
- const { timeout } = options;
29077
- const timer = timeout === undefined ? undefined : setTimeout(() => {
29078
- failure ??= `it ran past its ${timeout.said}`;
29079
- child.kill();
29080
- }, timeout.ms);
29081
- child.on("error", (error) => {
29082
- failure ??= error.name === "AbortError" ? "the call was cancelled" : `it could not be started: ${error.message}`;
29083
- });
29084
- child.on("close", (code, closedBy) => {
29085
- clearTimeout(timer);
29086
- log.finish();
29087
- const started = child.pid !== undefined;
29088
- resolve({ log, exitCode: started ? code : null, exitSignal: closedBy, failure });
29089
- });
29090
- });
29091
- }
29092
- function howItEnded(run) {
29093
- if (run.failure !== null) {
29094
- return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
29095
- }
29096
- return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
29097
- }
29098
-
29099
29174
  // src/file-move.ts
29100
29175
  import {
29101
- existsSync as existsSync12,
29176
+ existsSync as existsSync13,
29102
29177
  mkdirSync as mkdirSync10,
29103
- readdirSync as readdirSync5,
29178
+ readdirSync as readdirSync6,
29104
29179
  readFileSync as readFileSync16,
29105
29180
  renameSync as renameSync3,
29106
- statSync as statSync6,
29181
+ statSync as statSync7,
29107
29182
  writeFileSync as writeFileSync11
29108
29183
  } from "node:fs";
29109
- import { basename as basename3, dirname as dirname11, extname as extname3, join as join15, relative as relative4 } from "node:path";
29184
+ import { basename as basename4, dirname as dirname12, extname as extname3, join as join16, relative as relative4 } from "node:path";
29110
29185
 
29111
29186
  // src/project-scan.ts
29112
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync13 } from "node:fs";
29113
- import { extname, join as join12 } from "node:path";
29187
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync13 } from "node:fs";
29188
+ import { extname, join as join13 } from "node:path";
29114
29189
  var SKIPPED = new Set(["node_modules"]);
29115
29190
  function skipped(name) {
29116
29191
  return name.startsWith(".") || SKIPPED.has(name);
@@ -29120,10 +29195,10 @@ function steppedOver(directory) {
29120
29195
  return gdignored(directory) || installedByUs(directory);
29121
29196
  }
29122
29197
  function gdignored(directory) {
29123
- return existsSync8(join12(directory, ".gdignore"));
29198
+ return existsSync9(join13(directory, ".gdignore"));
29124
29199
  }
29125
29200
  function installedByUs(directory) {
29126
- return existsSync8(join12(directory, VERSION_MARKER));
29201
+ return existsSync9(join13(directory, VERSION_MARKER));
29127
29202
  }
29128
29203
  var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
29129
29204
  var ASSET_EXTENSIONS = new Set([
@@ -29162,12 +29237,12 @@ function projectStructure(projectPath) {
29162
29237
  if (steppedOver(directory)) {
29163
29238
  return;
29164
29239
  }
29165
- for (const entry of readdirSync3(directory, { withFileTypes: true })) {
29240
+ for (const entry of readdirSync4(directory, { withFileTypes: true })) {
29166
29241
  if (skipped(entry.name)) {
29167
29242
  continue;
29168
29243
  }
29169
29244
  if (entry.isDirectory()) {
29170
- visit(join12(directory, entry.name));
29245
+ visit(join13(directory, entry.name));
29171
29246
  } else if (entry.isFile()) {
29172
29247
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
29173
29248
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -29191,13 +29266,13 @@ function projectFiles(projectPath, extensions) {
29191
29266
  if (steppedOver(directory)) {
29192
29267
  return;
29193
29268
  }
29194
- for (const entry of readdirSync3(directory, { withFileTypes: true })) {
29269
+ for (const entry of readdirSync4(directory, { withFileTypes: true })) {
29195
29270
  if (skipped(entry.name)) {
29196
29271
  continue;
29197
29272
  }
29198
29273
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
29199
29274
  if (entry.isDirectory()) {
29200
- visit(join12(directory, entry.name), spelled);
29275
+ visit(join13(directory, entry.name), spelled);
29201
29276
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
29202
29277
  found.push(spelled);
29203
29278
  }
@@ -29213,20 +29288,20 @@ function scriptsWithoutUid(projectPath) {
29213
29288
  if (steppedOver(directory)) {
29214
29289
  return;
29215
29290
  }
29216
- for (const entry of readdirSync3(directory, { withFileTypes: true })) {
29291
+ for (const entry of readdirSync4(directory, { withFileTypes: true })) {
29217
29292
  if (skipped(entry.name)) {
29218
29293
  continue;
29219
29294
  }
29220
29295
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
29221
29296
  if (entry.isDirectory()) {
29222
- visit(join12(directory, entry.name), spelled);
29297
+ visit(join13(directory, entry.name), spelled);
29223
29298
  continue;
29224
29299
  }
29225
29300
  if (!entry.isFile()) {
29226
29301
  continue;
29227
29302
  }
29228
29303
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
29229
- if (CARRIES_A_UID.has(extension) && !existsSync8(join12(directory, `${entry.name}.uid`))) {
29304
+ if (CARRIES_A_UID.has(extension) && !existsSync9(join13(directory, `${entry.name}.uid`))) {
29230
29305
  found.push(spelled);
29231
29306
  }
29232
29307
  }
@@ -29254,11 +29329,11 @@ function projectTexts(projectPath) {
29254
29329
  if (steppedOver(directory)) {
29255
29330
  return;
29256
29331
  }
29257
- for (const entry of readdirSync3(directory, { withFileTypes: true })) {
29332
+ for (const entry of readdirSync4(directory, { withFileTypes: true })) {
29258
29333
  if (skipped(entry.name)) {
29259
29334
  continue;
29260
29335
  }
29261
- const absolute = join12(directory, entry.name);
29336
+ const absolute = join13(directory, entry.name);
29262
29337
  const spelled = `${prefix}${entry.name}`;
29263
29338
  if (entry.isDirectory()) {
29264
29339
  visit(absolute, `${spelled}/`);
@@ -29284,11 +29359,11 @@ function ignoredTexts(projectPath) {
29284
29359
  return;
29285
29360
  }
29286
29361
  const under = ignored || gdignored(directory);
29287
- for (const entry of readdirSync3(directory, { withFileTypes: true })) {
29362
+ for (const entry of readdirSync4(directory, { withFileTypes: true })) {
29288
29363
  if (skipped(entry.name)) {
29289
29364
  continue;
29290
29365
  }
29291
- const absolute = join12(directory, entry.name);
29366
+ const absolute = join13(directory, entry.name);
29292
29367
  const spelled = `${prefix}${entry.name}`;
29293
29368
  if (entry.isDirectory()) {
29294
29369
  visit(absolute, `${spelled}/`, under);
@@ -29321,14 +29396,14 @@ function searchProject(projectPath, options) {
29321
29396
  if (steppedOver(directory)) {
29322
29397
  return;
29323
29398
  }
29324
- for (const entry of readdirSync3(directory, { withFileTypes: true })) {
29399
+ for (const entry of readdirSync4(directory, { withFileTypes: true })) {
29325
29400
  if (full()) {
29326
29401
  return;
29327
29402
  }
29328
29403
  if (skipped(entry.name)) {
29329
29404
  continue;
29330
29405
  }
29331
- const entryPath = join12(directory, entry.name);
29406
+ const entryPath = join13(directory, entry.name);
29332
29407
  if (entry.isDirectory()) {
29333
29408
  visit(entryPath);
29334
29409
  continue;
@@ -29367,8 +29442,8 @@ function searchProject(projectPath, options) {
29367
29442
  }
29368
29443
 
29369
29444
  // src/rename.ts
29370
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
29371
- import { dirname as dirname8, posix as posix2 } from "node:path";
29445
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
29446
+ import { dirname as dirname9, posix as posix2 } from "node:path";
29372
29447
  class Script {
29373
29448
  path;
29374
29449
  text;
@@ -30097,21 +30172,21 @@ function applyRewrites(rewrites) {
30097
30172
  if (now !== rewrite.before) {
30098
30173
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
30099
30174
  }
30100
- if (rewrite.writeTo !== rewrite.absolute && existsSync9(rewrite.writeTo)) {
30175
+ if (rewrite.writeTo !== rewrite.absolute && existsSync10(rewrite.writeTo)) {
30101
30176
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
30102
30177
  }
30103
30178
  }
30104
30179
  const done = [];
30105
30180
  try {
30106
30181
  for (const rewrite of rewrites) {
30107
- mkdirSync8(dirname8(rewrite.writeTo), { recursive: true });
30182
+ mkdirSync8(dirname9(rewrite.writeTo), { recursive: true });
30108
30183
  const staging = `${rewrite.writeTo}.gdharness-rename`;
30109
30184
  writeFileSync9(staging, rewrite.after, "utf8");
30110
30185
  renameSync2(staging, rewrite.writeTo);
30111
30186
  let movedUid = false;
30112
30187
  if (rewrite.writeTo !== rewrite.absolute) {
30113
30188
  unlinkSync2(rewrite.absolute);
30114
- if (existsSync9(`${rewrite.absolute}.uid`)) {
30189
+ if (existsSync10(`${rewrite.absolute}.uid`)) {
30115
30190
  renameSync2(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
30116
30191
  movedUid = true;
30117
30192
  }
@@ -30135,14 +30210,14 @@ function applyRewrites(rewrites) {
30135
30210
  }
30136
30211
 
30137
30212
  // src/rename-symbol.ts
30138
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync4, realpathSync as realpathSync4, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
30139
- import { basename as basename2, dirname as dirname10, join as join14, relative as relative3, resolve as resolve5 } from "node:path";
30213
+ import { existsSync as existsSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync5, realpathSync as realpathSync4, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
30214
+ import { basename as basename3, dirname as dirname11, join as join15, relative as relative3, resolve as resolve5 } from "node:path";
30140
30215
 
30141
30216
  // src/lsp_client.ts
30142
- import { existsSync as existsSync10, realpathSync as realpathSync2 } from "node:fs";
30217
+ import { existsSync as existsSync11, realpathSync as realpathSync2 } from "node:fs";
30143
30218
  import { readFile, realpath } from "node:fs/promises";
30144
30219
  import { createConnection as createConnection3 } from "node:net";
30145
- import { dirname as dirname9, join as join13, resolve as resolve3 } from "node:path";
30220
+ import { dirname as dirname10, join as join14, resolve as resolve3 } from "node:path";
30146
30221
  import { setTimeout as delay4 } from "node:timers/promises";
30147
30222
  import { fileURLToPath, pathToFileURL } from "node:url";
30148
30223
  var DEFAULT_LSP_PORT = 6005;
@@ -30340,7 +30415,7 @@ class GodotLSPClient {
30340
30415
  if (this.initialized) {
30341
30416
  return;
30342
30417
  }
30343
- const rootPath = this.rootPath ?? dirname9(resolve3(filePath));
30418
+ const rootPath = this.rootPath ?? dirname10(resolve3(filePath));
30344
30419
  await this.initialize(rootPath);
30345
30420
  }
30346
30421
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -30913,7 +30988,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
30913
30988
  } catch {
30914
30989
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
30915
30990
  }
30916
- if (!existsSync10(join13(projectPath, "project.godot"))) {
30991
+ if (!existsSync11(join14(projectPath, "project.godot"))) {
30917
30992
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
30918
30993
  }
30919
30994
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -31288,13 +31363,13 @@ function onDiskSpelling(path) {
31288
31363
  let at = resolve5(path);
31289
31364
  for (;; ) {
31290
31365
  try {
31291
- return join14(realpathSync4.native(at), ...missing.reverse());
31366
+ return join15(realpathSync4.native(at), ...missing.reverse());
31292
31367
  } catch {
31293
- const parent = dirname10(at);
31368
+ const parent = dirname11(at);
31294
31369
  if (parent === at) {
31295
31370
  return resolve5(path);
31296
31371
  }
31297
- missing.push(basename2(at));
31372
+ missing.push(basename3(at));
31298
31373
  at = parent;
31299
31374
  }
31300
31375
  }
@@ -31341,7 +31416,7 @@ async function renameSymbol(request, services) {
31341
31416
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
31342
31417
  };
31343
31418
  }
31344
- if (existsSync11(request.newScriptPath)) {
31419
+ if (existsSync12(request.newScriptPath)) {
31345
31420
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
31346
31421
  }
31347
31422
  }
@@ -31517,7 +31592,7 @@ async function renameSymbol(request, services) {
31517
31592
  scripts,
31518
31593
  moves: new Map([[declaringPath, moveTo]]),
31519
31594
  named: [declaringPath],
31520
- exists: (path) => existsSync11(`${projectPath}/${path.slice("res://".length)}`)
31595
+ exists: (path) => existsSync12(`${projectPath}/${path.slice("res://".length)}`)
31521
31596
  }, plan);
31522
31597
  }
31523
31598
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
@@ -31718,15 +31793,15 @@ ${mention.kind}`;
31718
31793
  var LEFT_ALONE_ANSWER_LINES = 300;
31719
31794
  var detailWrites = 0;
31720
31795
  function writeLeftAloneDetail(projectPath, symbol, detail) {
31721
- const directory = join14(projectPath, ".godot", "gdharness-renames");
31796
+ const directory = join15(projectPath, ".godot", "gdharness-renames");
31722
31797
  mkdirSync9(directory, { recursive: true });
31723
31798
  detailWrites += 1;
31724
- const path = join14(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
31799
+ const path = join15(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
31725
31800
  writeFileSync10(path, `${JSON.stringify(detail, null, 2)}
31726
31801
  `, "utf8");
31727
- const kept = readdirSync4(directory).filter((name) => name.endsWith(".json")).sort().reverse();
31802
+ const kept = readdirSync5(directory).filter((name) => name.endsWith(".json")).sort().reverse();
31728
31803
  for (const old of kept.slice(10)) {
31729
- rmSync7(join14(directory, old), { force: true });
31804
+ rmSync7(join15(directory, old), { force: true });
31730
31805
  }
31731
31806
  return path;
31732
31807
  }
@@ -31751,15 +31826,15 @@ function summaryOf(edits, mentions, rewrites, left) {
31751
31826
  // src/file-move.ts
31752
31827
  var SIDECARS = [".uid", ".import"];
31753
31828
  function filesUnder(directory) {
31754
- return readdirSync5(directory, { withFileTypes: true }).flatMap((entry) => {
31755
- const path = join15(directory, entry.name);
31829
+ return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
31830
+ const path = join16(directory, entry.name);
31756
31831
  return entry.isDirectory() ? filesUnder(path) : [path];
31757
31832
  });
31758
31833
  }
31759
31834
  function sameFile(one, other) {
31760
31835
  try {
31761
- const a = statSync6(one);
31762
- const b = statSync6(other);
31836
+ const a = statSync7(one);
31837
+ const b = statSync7(other);
31763
31838
  return a.ino === b.ino && a.dev === b.dev;
31764
31839
  } catch {
31765
31840
  return false;
@@ -31773,8 +31848,8 @@ function unseenByTheEngine(projectPath, path) {
31773
31848
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
31774
31849
  }
31775
31850
  if (at < steps.length - 1) {
31776
- directory = join15(directory, step);
31777
- if (existsSync12(join15(directory, ".gdignore"))) {
31851
+ directory = join16(directory, step);
31852
+ if (existsSync13(join16(directory, ".gdignore"))) {
31778
31853
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
31779
31854
  }
31780
31855
  }
@@ -31786,21 +31861,21 @@ function installedAddonOf(projectPath, path) {
31786
31861
  if (steps[0] !== "addons" || steps[1] === undefined) {
31787
31862
  return null;
31788
31863
  }
31789
- return existsSync12(join15(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
31864
+ return existsSync13(join16(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
31790
31865
  }
31791
31866
  function binaryResources(projectPath) {
31792
31867
  const found = [];
31793
31868
  const visit = (directory, prefix) => {
31794
- if (existsSync12(join15(directory, ".gdignore"))) {
31869
+ if (existsSync13(join16(directory, ".gdignore"))) {
31795
31870
  return;
31796
31871
  }
31797
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
31872
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
31798
31873
  if (entry.name.startsWith(".")) {
31799
31874
  continue;
31800
31875
  }
31801
31876
  const path = `${prefix}${entry.name}`;
31802
31877
  if (entry.isDirectory()) {
31803
- visit(join15(directory, entry.name), `${path}/`);
31878
+ visit(join16(directory, entry.name), `${path}/`);
31804
31879
  } else if (/\.(scn|res)$/i.test(entry.name)) {
31805
31880
  found.push(path);
31806
31881
  }
@@ -31810,7 +31885,7 @@ function binaryResources(projectPath) {
31810
31885
  return found;
31811
31886
  }
31812
31887
  function targetPathOf(projectPath, absolute) {
31813
- const inside = relative4(onDiskSpelling(projectPath), join15(onDiskSpelling(dirname11(absolute)), basename3(absolute)));
31888
+ const inside = relative4(onDiskSpelling(projectPath), join16(onDiskSpelling(dirname12(absolute)), basename4(absolute)));
31814
31889
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
31815
31890
  return null;
31816
31891
  }
@@ -31828,13 +31903,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
31828
31903
  if (to === null) {
31829
31904
  return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
31830
31905
  }
31831
- if (!existsSync12(absoluteFrom)) {
31906
+ if (!existsSync13(absoluteFrom)) {
31832
31907
  return { ok: false, reason: `There is nothing at ${from} to move.` };
31833
31908
  }
31834
31909
  if (from === to) {
31835
31910
  return { ok: false, reason: `${from} is already there; nothing to move.` };
31836
31911
  }
31837
- const isDirectory = statSync6(absoluteFrom).isDirectory();
31912
+ const isDirectory = statSync7(absoluteFrom).isDirectory();
31838
31913
  if (from === "res://project.godot") {
31839
31914
  return {
31840
31915
  ok: false,
@@ -31857,7 +31932,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
31857
31932
  if (isDirectory && to.startsWith(`${from}/`)) {
31858
31933
  return { ok: false, reason: `${from} cannot be moved into itself.` };
31859
31934
  }
31860
- if (existsSync12(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
31935
+ if (existsSync13(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
31861
31936
  return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
31862
31937
  }
31863
31938
  const unseen = unseenByTheEngine(projectPath, to);
@@ -31865,7 +31940,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
31865
31940
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
31866
31941
  }
31867
31942
  if (!isDirectory) {
31868
- const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync12(join15(projectPath, path.slice("res://".length))));
31943
+ const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync13(join16(projectPath, path.slice("res://".length))));
31869
31944
  if (owner !== undefined && owner !== null) {
31870
31945
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
31871
31946
  }
@@ -31887,7 +31962,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
31887
31962
  } else {
31888
31963
  moves.set(from, to);
31889
31964
  for (const sidecar of SIDECARS) {
31890
- if (existsSync12(`${absoluteFrom}${sidecar}`)) {
31965
+ if (existsSync13(`${absoluteFrom}${sidecar}`)) {
31891
31966
  moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
31892
31967
  }
31893
31968
  }
@@ -31899,12 +31974,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
31899
31974
  scripts,
31900
31975
  moves,
31901
31976
  named: [from],
31902
- exists: (path) => existsSync12(join15(projectPath, path.slice("res://".length)))
31977
+ exists: (path) => existsSync13(join16(projectPath, path.slice("res://".length)))
31903
31978
  }, { edits: [], mentions: [] });
31904
31979
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
31905
31980
  const binaries = [];
31906
31981
  for (const path of binaryResources(projectPath)) {
31907
- const bytes = readFileSync16(join15(projectPath, path.slice("res://".length)));
31982
+ const bytes = readFileSync16(join16(projectPath, path.slice("res://".length)));
31908
31983
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
31909
31984
  if (names !== undefined) {
31910
31985
  binaries.push({
@@ -31923,7 +31998,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
31923
31998
  }));
31924
31999
  const left = leftAloneReport(mentions);
31925
32000
  const rewrites = rewritesOf(plan, texts, null);
31926
- const absoluteOf = (path) => join15(projectPath, path.slice("res://".length));
32001
+ const absoluteOf = (path) => join16(projectPath, path.slice("res://".length));
31927
32002
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
31928
32003
  const moved = [...moves].map(([source, target]) => {
31929
32004
  const rewrite = rewritten.get(source);
@@ -31970,19 +32045,19 @@ function applyFileMove(outcome) {
31970
32045
  }
31971
32046
  }
31972
32047
  for (const file of outcome.moved) {
31973
- if (!existsSync12(file.absoluteFrom)) {
32048
+ if (!existsSync13(file.absoluteFrom)) {
31974
32049
  throw new Error(`${file.from} is gone from disk; nothing was written`);
31975
32050
  }
31976
32051
  if (file.text !== null && readFileSync16(file.absoluteFrom, "utf8") !== file.text.before) {
31977
32052
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
31978
32053
  }
31979
- if (existsSync12(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
32054
+ if (existsSync13(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
31980
32055
  throw new Error(`${file.to} already exists; nothing was written`);
31981
32056
  }
31982
32057
  }
31983
32058
  const undo = [];
31984
32059
  const rename = (source, target) => {
31985
- mkdirSync10(dirname11(target), { recursive: true });
32060
+ mkdirSync10(dirname12(target), { recursive: true });
31986
32061
  renameSync3(source, target);
31987
32062
  undo.push(() => {
31988
32063
  renameSync3(target, source);
@@ -32705,27 +32780,27 @@ function getDefaultBridge(ownProject = null) {
32705
32780
  }
32706
32781
 
32707
32782
  // src/godot-path.ts
32708
- import { existsSync as existsSync14 } from "node:fs";
32783
+ import { existsSync as existsSync15 } from "node:fs";
32709
32784
  import { normalize } from "node:path";
32710
32785
 
32711
32786
  // src/detection.ts
32712
- import { existsSync as existsSync13, readdirSync as readdirSync6, statSync as statSync7 } from "node:fs";
32713
- import { homedir as homedir2 } from "node:os";
32714
- import { join as join16 } from "node:path";
32787
+ import { existsSync as existsSync14, readdirSync as readdirSync7, statSync as statSync8 } from "node:fs";
32788
+ import { homedir as homedir3 } from "node:os";
32789
+ import { join as join17 } from "node:path";
32715
32790
  function resolveHomeDirectory() {
32716
32791
  try {
32717
- return homedir2();
32792
+ return homedir3();
32718
32793
  } catch {
32719
32794
  return "";
32720
32795
  }
32721
32796
  }
32722
32797
  function scanDirectoryForGodotBinaries(directory, platform) {
32723
- if (!directory || !existsSync13(directory)) {
32798
+ if (!directory || !existsSync14(directory)) {
32724
32799
  return [];
32725
32800
  }
32726
32801
  let entries;
32727
32802
  try {
32728
- entries = readdirSync6(directory);
32803
+ entries = readdirSync7(directory);
32729
32804
  } catch {
32730
32805
  return [];
32731
32806
  }
@@ -32735,16 +32810,16 @@ function scanDirectoryForGodotBinaries(directory, platform) {
32735
32810
  if (!pattern.test(name)) {
32736
32811
  continue;
32737
32812
  }
32738
- const fullPath = join16(directory, name);
32813
+ const fullPath = join17(directory, name);
32739
32814
  try {
32740
- const stat = statSync7(fullPath);
32815
+ const stat = statSync8(fullPath);
32741
32816
  if (stat.isFile()) {
32742
32817
  matches.push({ name, mtime: stat.mtimeMs });
32743
32818
  }
32744
32819
  } catch {}
32745
32820
  }
32746
32821
  matches.sort((a, b) => b.mtime - a.mtime);
32747
- return matches.map((m) => join16(directory, m.name));
32822
+ return matches.map((m) => join17(directory, m.name));
32748
32823
  }
32749
32824
  function conventionalPaths(platform, home) {
32750
32825
  const paths = ["godot"];
@@ -32845,7 +32920,7 @@ class GodotLocator {
32845
32920
  return known;
32846
32921
  }
32847
32922
  let reason;
32848
- if (path !== "godot" && !existsSync14(path)) {
32923
+ if (path !== "godot" && !existsSync15(path)) {
32849
32924
  reason = "does not exist";
32850
32925
  } else {
32851
32926
  try {
@@ -32861,12 +32936,12 @@ class GodotLocator {
32861
32936
  }
32862
32937
 
32863
32938
  // src/headless.ts
32864
- import { existsSync as existsSync18, readFileSync as readFileSync19, realpathSync as realpathSync5, writeFileSync as writeFileSync13 } from "node:fs";
32865
- import { dirname as dirname13, join as join20 } from "node:path";
32939
+ import { existsSync as existsSync19, readFileSync as readFileSync19, realpathSync as realpathSync5, writeFileSync as writeFileSync13 } from "node:fs";
32940
+ import { dirname as dirname14, join as join21 } from "node:path";
32866
32941
 
32867
32942
  // src/library-copies.ts
32868
- import { existsSync as existsSync15, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync4, unlinkSync as unlinkSync3 } from "node:fs";
32869
- import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute3, join as join17 } from "node:path";
32943
+ import { existsSync as existsSync16, readdirSync as readdirSync8, readFileSync as readFileSync17, renameSync as renameSync4, unlinkSync as unlinkSync3 } from "node:fs";
32944
+ import { basename as basename5, dirname as dirname13, isAbsolute as isAbsolute4, join as join18 } from "node:path";
32870
32945
  function freeLibraryCopies(projectPath, platform = process.platform) {
32871
32946
  const setAside = [];
32872
32947
  const swept = [];
@@ -32874,26 +32949,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
32874
32949
  return { setAside, swept };
32875
32950
  }
32876
32951
  for (const library of windowsLibraries(projectPath)) {
32877
- const folder = dirname12(library);
32878
- const copy = `~${basename4(library)}`;
32879
- if (existsSync15(join17(folder, copy))) {
32880
- const aside = join17(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
32952
+ const folder = dirname13(library);
32953
+ const copy = `~${basename5(library)}`;
32954
+ if (existsSync16(join18(folder, copy))) {
32955
+ const aside = join18(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
32881
32956
  try {
32882
- renameSync4(join17(folder, copy), aside);
32957
+ renameSync4(join18(folder, copy), aside);
32883
32958
  setAside.push(aside);
32884
32959
  } catch {}
32885
32960
  }
32886
32961
  let names;
32887
32962
  try {
32888
- names = readdirSync7(folder);
32963
+ names = readdirSync8(folder);
32889
32964
  } catch {
32890
32965
  continue;
32891
32966
  }
32892
32967
  for (const name of names) {
32893
32968
  if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
32894
32969
  try {
32895
- unlinkSync3(join17(folder, name));
32896
- swept.push(join17(folder, name));
32970
+ unlinkSync3(join18(folder, name));
32971
+ swept.push(join18(folder, name));
32897
32972
  } catch {}
32898
32973
  }
32899
32974
  }
@@ -32901,7 +32976,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
32901
32976
  return { setAside, swept };
32902
32977
  }
32903
32978
  function windowsLibraries(projectPath) {
32904
- const listed = readOrNull(join17(projectPath, ".godot", "extension_list.cfg"));
32979
+ const listed = readOrNull(join18(projectPath, ".godot", "extension_list.cfg"));
32905
32980
  if (listed === null) {
32906
32981
  return [];
32907
32982
  }
@@ -32920,7 +32995,7 @@ function windowsLibraries(projectPath) {
32920
32995
  continue;
32921
32996
  }
32922
32997
  const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
32923
- const library = named === undefined ? null : resolveResource(projectPath, dirname12(manifest), named);
32998
+ const library = named === undefined ? null : resolveResource(projectPath, dirname13(manifest), named);
32924
32999
  if (library !== null) {
32925
33000
  libraries.add(library);
32926
33001
  }
@@ -32933,9 +33008,9 @@ function resolveResource(projectPath, relativeTo, path) {
32933
33008
  return null;
32934
33009
  }
32935
33010
  if (path.startsWith("res://")) {
32936
- return join17(projectPath, path.slice("res://".length));
33011
+ return join18(projectPath, path.slice("res://".length));
32937
33012
  }
32938
- return isAbsolute3(path) ? path : join17(relativeTo, path);
33013
+ return isAbsolute4(path) ? path : join18(relativeTo, path);
32939
33014
  }
32940
33015
  function readOrNull(path) {
32941
33016
  try {
@@ -32947,29 +33022,29 @@ function readOrNull(path) {
32947
33022
 
32948
33023
  // src/reimport.ts
32949
33024
  import { createHash as createHash3 } from "node:crypto";
32950
- import { existsSync as existsSync16, readFileSync as readFileSync18, rmSync as rmSync8, statSync as statSync8, utimesSync, writeFileSync as writeFileSync12 } from "node:fs";
32951
- import { basename as basename5, join as join18 } from "node:path";
33025
+ import { existsSync as existsSync17, readFileSync as readFileSync18, rmSync as rmSync8, statSync as statSync9, utimesSync, writeFileSync as writeFileSync12 } from "node:fs";
33026
+ import { basename as basename6, join as join19 } from "node:path";
32952
33027
  function forceNextImport(projectPath, resourcePath) {
32953
33028
  const relative = resourcePath.replace(/^res:\/\//, "");
32954
- const sidecar = join18(projectPath, `${relative}.import`);
32955
- if (!existsSync16(sidecar)) {
33029
+ const sidecar = join19(projectPath, `${relative}.import`);
33030
+ if (!existsSync17(sidecar)) {
32956
33031
  return;
32957
33032
  }
32958
- const { atime, mtimeMs } = statSync8(sidecar);
33033
+ const { atime, mtimeMs } = statSync9(sidecar);
32959
33034
  writeFileSync12(sidecar, readFileSync18(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
32960
33035
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
32961
33036
  rmSync8(importRecord(projectPath, resourcePath), { force: true });
32962
33037
  }
32963
33038
  function importRecord(projectPath, resourcePath) {
32964
33039
  const digest = createHash3("md5").update(resourcePath).digest("hex");
32965
- return join18(projectPath, ".godot", "imported", `${basename5(resourcePath)}-${digest}.md5`);
33040
+ return join19(projectPath, ".godot", "imported", `${basename6(resourcePath)}-${digest}.md5`);
32966
33041
  }
32967
33042
 
32968
33043
  // src/scratch.ts
32969
- import { existsSync as existsSync17, mkdtempSync as mkdtempSync2, readdirSync as readdirSync8, rmSync as rmSync9 } from "node:fs";
33044
+ import { existsSync as existsSync18, mkdtempSync as mkdtempSync2, readdirSync as readdirSync9, rmSync as rmSync9 } from "node:fs";
32970
33045
  import { tmpdir as tmpdir5 } from "node:os";
32971
- import { join as join19 } from "node:path";
32972
- import process7 from "node:process";
33046
+ import { join as join20 } from "node:path";
33047
+ import process8 from "node:process";
32973
33048
  var SWEPT = { recursive: true, force: true };
32974
33049
  var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
32975
33050
  var waiting = new Set;
@@ -32984,12 +33059,12 @@ var KINDS = [
32984
33059
  "tests"
32985
33060
  ];
32986
33061
  function scratchDirectory(kind) {
32987
- return mkdtempSync2(join19(tmpdir5(), `gdharness-${kind}-${process7.pid}-`));
33062
+ return mkdtempSync2(join20(tmpdir5(), `gdharness-${kind}-${process8.pid}-`));
32988
33063
  }
32989
33064
  function sweepAbandonedScratch(directory = tmpdir5()) {
32990
33065
  let entries;
32991
33066
  try {
32992
- entries = readdirSync8(directory);
33067
+ entries = readdirSync9(directory);
32993
33068
  } catch {
32994
33069
  return;
32995
33070
  }
@@ -33000,7 +33075,7 @@ function sweepAbandonedScratch(directory = tmpdir5()) {
33000
33075
  continue;
33001
33076
  }
33002
33077
  try {
33003
- remove(join19(directory, entry));
33078
+ remove(join20(directory, entry));
33004
33079
  } catch {}
33005
33080
  }
33006
33081
  }
@@ -33038,7 +33113,7 @@ function tryAgain(path, attempt) {
33038
33113
  lastTryOnExit();
33039
33114
  }
33040
33115
  const after = AGAIN_AFTER_MS[attempt];
33041
- if (after === undefined || !existsSync17(path)) {
33116
+ if (after === undefined || !existsSync18(path)) {
33042
33117
  waiting.delete(path);
33043
33118
  if (after === undefined) {
33044
33119
  atExit.add(path);
@@ -33060,7 +33135,7 @@ function lastTryOnExit() {
33060
33135
  return;
33061
33136
  }
33062
33137
  lastTryWatched = true;
33063
- process7.once("exit", () => {
33138
+ process8.once("exit", () => {
33064
33139
  for (const path of [...waiting, ...atExit]) {
33065
33140
  try {
33066
33141
  remove(path);
@@ -33138,7 +33213,7 @@ function refusalsIn(log) {
33138
33213
  }
33139
33214
  function scriptFault(problems, script) {
33140
33215
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
33141
- const directories = [...new Set([spelled(dirname13(script)), spelled(resolvedOr(dirname13(script)))])];
33216
+ const directories = [...new Set([spelled(dirname14(script)), spelled(resolvedOr(dirname14(script)))])];
33142
33217
  return problems.find((entry) => {
33143
33218
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
33144
33219
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
@@ -33176,26 +33251,33 @@ async function runImport(godotPath, projectPath, options = {}) {
33176
33251
  };
33177
33252
  const first = await pass();
33178
33253
  const collided = librariesNotCopied(first.messages);
33179
- if (collided.length === 0) {
33254
+ const crash = first.docCacheNote;
33255
+ if (collided.length === 0 && crash === undefined) {
33180
33256
  return first;
33181
33257
  }
33182
- const second = await pass();
33258
+ const { docCacheNote: _, ...again } = await pass();
33259
+ const second = again.ok || crash === undefined ? again : { ...again, message: `${again.message.replace(/[.\s]+$/, "")}. ${crash}` };
33183
33260
  const still = librariesNotCopied(second.messages);
33184
33261
  return {
33185
33262
  ...second,
33186
- librariesRetried: collided,
33187
- ...still.length === 0 ? {} : { librariesNotLoaded: still },
33188
- extensionNote: extensionRetryNote(collided, still)
33263
+ ...collided.length === 0 ? {} : {
33264
+ librariesRetried: collided,
33265
+ ...still.length === 0 ? {} : { librariesNotLoaded: still },
33266
+ extensionNote: extensionRetryNote(collided, still)
33267
+ },
33268
+ ...crash === undefined ? {} : { docCacheNote: crash }
33189
33269
  };
33190
33270
  }
33191
33271
  async function importOnce(godotPath, projectPath) {
33192
33272
  const logDir = scratchDirectory("import");
33273
+ const caches = editorCacheDirectory(godotPath);
33274
+ const cachesBefore = docCachesWritten(caches);
33193
33275
  let ran;
33194
33276
  try {
33195
33277
  ran = await runEngine(godotPath, [
33196
33278
  "--headless",
33197
33279
  "--log-file",
33198
- join20(logDir, "engine.log"),
33280
+ join21(logDir, "engine.log"),
33199
33281
  "--path",
33200
33282
  projectPath,
33201
33283
  "--import"
@@ -33204,24 +33286,26 @@ async function importOnce(godotPath, projectPath) {
33204
33286
  discard(logDir);
33205
33287
  }
33206
33288
  const problems = problemsIn(ran.log);
33289
+ const crash = docCacheCrash(ran, cachesBefore, docCachesWritten(caches), "import");
33207
33290
  if (ran.exitCode === 0 && ran.failure === null) {
33208
- return { ok: true, ...reported2(problems) };
33291
+ return { ok: true, ...reported2(problems), ...crash === null ? {} : { docCacheNote: crash } };
33209
33292
  }
33210
33293
  return {
33211
33294
  ok: false,
33212
33295
  message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
33213
- ...reported2(problems)
33296
+ ...reported2(problems),
33297
+ ...crash === null ? {} : { docCacheNote: crash }
33214
33298
  };
33215
33299
  }
33216
33300
  async function runOperation(engine, operation, params, projectPath) {
33217
33301
  const paramsDir = scratchDirectory("params");
33218
- const paramsFile = join20(paramsDir, `${operation}.json`);
33219
- const answerFile = join20(paramsDir, "answer.json");
33302
+ const paramsFile = join21(paramsDir, `${operation}.json`);
33303
+ const answerFile = join21(paramsDir, "answer.json");
33220
33304
  writeFileSync13(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
33221
33305
  const args = [
33222
33306
  "--headless",
33223
33307
  "--log-file",
33224
- join20(paramsDir, "engine.log"),
33308
+ join21(paramsDir, "engine.log"),
33225
33309
  "--path",
33226
33310
  projectPath,
33227
33311
  "--script",
@@ -33309,7 +33393,7 @@ async function runOperation(engine, operation, params, projectPath) {
33309
33393
  };
33310
33394
  }
33311
33395
  function answerIn(file) {
33312
- if (!existsSync18(file)) {
33396
+ if (!existsSync19(file)) {
33313
33397
  return { kind: "none" };
33314
33398
  }
33315
33399
  const text = readFileSync19(file, "utf8");
@@ -33935,26 +34019,26 @@ function askTheKeeper(address, line, withinMs) {
33935
34019
  }
33936
34020
 
33937
34021
  // src/lifecycle-log.ts
33938
- import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync5, statSync as statSync9 } from "node:fs";
33939
- import { dirname as dirname14, join as join21 } from "node:path";
33940
- import process8 from "node:process";
34022
+ import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync5, statSync as statSync10 } from "node:fs";
34023
+ import { dirname as dirname15, join as join22 } from "node:path";
34024
+ import process9 from "node:process";
33941
34025
  function lifecycleLogPath() {
33942
- return join21(runtimeDirectory(), "servers.log");
34026
+ return join22(runtimeDirectory(), "servers.log");
33943
34027
  }
33944
34028
  var LIFECYCLE_LOG_LIMIT = 1e6;
33945
34029
  function writeLifecycle(event, fields = {}) {
33946
34030
  const path = lifecycleLogPath();
33947
34031
  try {
33948
- mkdirSync11(dirname14(path), { recursive: true });
33949
- if ((statSync9(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
34032
+ mkdirSync11(dirname15(path), { recursive: true });
34033
+ if ((statSync10(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
33950
34034
  try {
33951
34035
  renameSync5(path, `${path}.1`);
33952
34036
  } catch {}
33953
34037
  }
33954
34038
  const line = {
33955
34039
  at: new Date().toISOString(),
33956
- pid: process8.pid,
33957
- ppid: process8.ppid,
34040
+ pid: process9.pid,
34041
+ ppid: process9.ppid,
33958
34042
  version: SERVER_VERSION,
33959
34043
  event
33960
34044
  };
@@ -34243,14 +34327,14 @@ function setupPromptHandlers(server) {
34243
34327
 
34244
34328
  // src/restart-note.ts
34245
34329
  import { mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
34246
- import { dirname as dirname15, join as join22 } from "node:path";
34330
+ import { dirname as dirname16, join as join23 } from "node:path";
34247
34331
  function restartNotePath(projectPath) {
34248
- return join22(projectPath, ".godot", "gdharness-restart.json");
34332
+ return join23(projectPath, ".godot", "gdharness-restart.json");
34249
34333
  }
34250
34334
  function noteRestartBegun(note) {
34251
34335
  const path = restartNotePath(note.projectPath);
34252
34336
  try {
34253
- mkdirSync12(dirname15(path), { recursive: true });
34337
+ mkdirSync12(dirname16(path), { recursive: true });
34254
34338
  writeFileSync14(path, `${JSON.stringify(note, null, 2)}
34255
34339
  `, "utf8");
34256
34340
  } catch (error) {
@@ -34282,23 +34366,23 @@ function restartSettled(projectPath) {
34282
34366
 
34283
34367
  // src/run-record.ts
34284
34368
  import { createHash as createHash4 } from "node:crypto";
34285
- import { mkdirSync as mkdirSync13, openSync as openSync3, readdirSync as readdirSync9, readFileSync as readFileSync21, realpathSync as realpathSync6, rmSync as rmSync11, statSync as statSync10 } from "node:fs";
34286
- import { basename as basename6, dirname as dirname16, join as join23, resolve as resolve6 } from "node:path";
34369
+ import { mkdirSync as mkdirSync13, openSync as openSync3, readdirSync as readdirSync10, readFileSync as readFileSync21, realpathSync as realpathSync6, rmSync as rmSync11, statSync as statSync11 } from "node:fs";
34370
+ import { basename as basename7, dirname as dirname17, join as join24, resolve as resolve6 } from "node:path";
34287
34371
  function runsDirectory() {
34288
- return join23(runtimeDirectory(), "runs");
34372
+ return join24(runtimeDirectory(), "runs");
34289
34373
  }
34290
34374
  function realAsFarAsItExists(path) {
34291
34375
  const missing = [];
34292
34376
  let at = path;
34293
34377
  for (;; ) {
34294
34378
  try {
34295
- return join23(realpathSync6.native(at), ...missing);
34379
+ return join24(realpathSync6.native(at), ...missing);
34296
34380
  } catch {
34297
- const parent = dirname16(at);
34381
+ const parent = dirname17(at);
34298
34382
  if (parent === at) {
34299
34383
  return path;
34300
34384
  }
34301
- missing.unshift(basename6(at));
34385
+ missing.unshift(basename7(at));
34302
34386
  at = parent;
34303
34387
  }
34304
34388
  }
@@ -34309,10 +34393,10 @@ function projectKey(projectPath) {
34309
34393
  return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
34310
34394
  }
34311
34395
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
34312
- return join23(directory, "runs", `note-${projectKey(projectPath)}.json`);
34396
+ return join24(directory, "runs", `note-${projectKey(projectPath)}.json`);
34313
34397
  }
34314
34398
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
34315
- return join23(directory, "runs", `latest-${projectKey(servedBy)}.json`);
34399
+ return join24(directory, "runs", `latest-${projectKey(servedBy)}.json`);
34316
34400
  }
34317
34401
  function writeLatestRun(servedBy, latest) {
34318
34402
  try {
@@ -34338,17 +34422,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
34338
34422
  var SHARED_NOTE = "run.json";
34339
34423
  function openTranscript(startedAt) {
34340
34424
  mkdirSync13(runsDirectory(), { recursive: true });
34341
- const path = join23(runsDirectory(), `run-${startedAt}.log`);
34425
+ const path = join24(runsDirectory(), `run-${startedAt}.log`);
34342
34426
  return { path, fd: openSync3(path, "a") };
34343
34427
  }
34344
34428
  function profilePath(startedAt) {
34345
- return join23(runsDirectory(), `run-${startedAt}.profile.json`);
34429
+ return join24(runsDirectory(), `run-${startedAt}.profile.json`);
34430
+ }
34431
+ function profileMarkPath(startedAt) {
34432
+ return join24(runsDirectory(), `run-${startedAt}.profile-mark.json`);
34346
34433
  }
34347
34434
  function noteIsAbout(note, playing) {
34348
34435
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
34349
34436
  }
34350
34437
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
34351
- return join23(directory, "runs", `editor-${projectKey(projectPath)}.json`);
34438
+ return join24(directory, "runs", `editor-${projectKey(projectPath)}.json`);
34352
34439
  }
34353
34440
  function writeEditorRunNote(note) {
34354
34441
  mkdirSync13(runsDirectory(), { recursive: true });
@@ -34359,7 +34446,7 @@ function writeEditorRunNote(note) {
34359
34446
  }
34360
34447
  function readEditorRunNote(projectPath) {
34361
34448
  for (const directory of runtimeDirectories()) {
34362
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join23(directory, "runs", "editor-run.json"));
34449
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join24(directory, "runs", "editor-run.json"));
34363
34450
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
34364
34451
  return note;
34365
34452
  }
@@ -34393,7 +34480,7 @@ function readRunRecord(projectPath) {
34393
34480
  if (own !== null) {
34394
34481
  return own;
34395
34482
  }
34396
- const shared = recordAt(join23(directory, "runs", SHARED_NOTE));
34483
+ const shared = recordAt(join24(directory, "runs", SHARED_NOTE));
34397
34484
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
34398
34485
  return shared;
34399
34486
  }
@@ -34405,13 +34492,13 @@ function everyRunRecord() {
34405
34492
  for (const directory of runtimeDirectories()) {
34406
34493
  let entries;
34407
34494
  try {
34408
- entries = readdirSync9(join23(directory, "runs"));
34495
+ entries = readdirSync10(join24(directory, "runs"));
34409
34496
  } catch {
34410
34497
  continue;
34411
34498
  }
34412
34499
  for (const entry of entries) {
34413
34500
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
34414
- const record = recordAt(join23(directory, "runs", entry));
34501
+ const record = recordAt(join24(directory, "runs", entry));
34415
34502
  if (record !== null) {
34416
34503
  found.push(record);
34417
34504
  }
@@ -34581,7 +34668,7 @@ function whyNotTheRun(record, running, needed) {
34581
34668
  if (after > SAME_RUN_WINDOW_MS) {
34582
34669
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
34583
34670
  }
34584
- const engine = record.command === undefined ? null : basename6(record.command);
34671
+ const engine = record.command === undefined ? null : basename7(record.command);
34585
34672
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
34586
34673
  const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
34587
34674
  if (wanted !== null && !said.includes(wanted)) {
@@ -34620,7 +34707,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
34620
34707
  }
34621
34708
  function clearRunRecord(projectPath) {
34622
34709
  for (const directory of runtimeDirectories()) {
34623
- const shared = join23(directory, "runs", SHARED_NOTE);
34710
+ const shared = join24(directory, "runs", SHARED_NOTE);
34624
34711
  const sharedRecord = recordAt(shared);
34625
34712
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
34626
34713
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
@@ -34632,7 +34719,7 @@ function clearRunRecord(projectPath) {
34632
34719
  }
34633
34720
  function ageMs(path, now) {
34634
34721
  try {
34635
- return now - statSync10(path).mtimeMs;
34722
+ return now - statSync11(path).mtimeMs;
34636
34723
  } catch {
34637
34724
  return 0;
34638
34725
  }
@@ -34641,20 +34728,21 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
34641
34728
  const directory = runsDirectory();
34642
34729
  const kept = new Set(everyRunRecord().flatMap((record) => [
34643
34730
  record.transcript,
34731
+ profileMarkPath(record.startedAt),
34644
34732
  ...record.profile === undefined ? [] : [record.profile]
34645
34733
  ]));
34646
34734
  let entries;
34647
34735
  try {
34648
- entries = readdirSync9(directory);
34736
+ entries = readdirSync10(directory);
34649
34737
  } catch {
34650
34738
  return;
34651
34739
  }
34652
34740
  for (const entry of entries) {
34653
- const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json"));
34741
+ const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json") || entry.endsWith(".profile-mark.json"));
34654
34742
  if (!transcript && !entry.endsWith(".partial")) {
34655
34743
  continue;
34656
34744
  }
34657
- const path = join23(directory, entry);
34745
+ const path = join24(directory, entry);
34658
34746
  if (kept.has(path) || ageMs(path, now) < keepMs) {
34659
34747
  continue;
34660
34748
  }
@@ -34665,7 +34753,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
34665
34753
  }
34666
34754
 
34667
34755
  // src/script-origins.ts
34668
- import { join as join24 } from "node:path";
34756
+ import { join as join25 } from "node:path";
34669
34757
  var UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
34670
34758
  function unresolvedClassIn(message) {
34671
34759
  return UNRESOLVED.exec(message)?.[1] ?? null;
@@ -34721,7 +34809,7 @@ function checkWithEngine(godotPath, projectPath) {
34721
34809
  const ran = await runEngine(godotPath, [
34722
34810
  "--headless",
34723
34811
  "--log-file",
34724
- join24(logDir, "engine.log"),
34812
+ join25(logDir, "engine.log"),
34725
34813
  "--path",
34726
34814
  projectPath,
34727
34815
  "--check-only",
@@ -34815,6 +34903,52 @@ function isCalibration(signature) {
34815
34903
  const parts = signatureParts(signature);
34816
34904
  return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
34817
34905
  }
34906
+ function writeProfileMark(path, mark) {
34907
+ return writeWhole(path, JSON.stringify(mark));
34908
+ }
34909
+ function readProfileMark(path) {
34910
+ try {
34911
+ const parsed = JSON.parse(readFileSync22(path, "utf8"));
34912
+ return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
34913
+ } catch {
34914
+ return null;
34915
+ }
34916
+ }
34917
+ function totalsSince(now, mark) {
34918
+ if (now.frames < mark.frames || now.startedAt !== mark.startedAt) {
34919
+ return null;
34920
+ }
34921
+ let shrunk = 0;
34922
+ const functions = {};
34923
+ for (const [signature, sum] of Object.entries(now.functions)) {
34924
+ const before = mark.functions[signature];
34925
+ if (isCalibration(signature) || before === undefined) {
34926
+ functions[signature] = sum;
34927
+ continue;
34928
+ }
34929
+ const added = {
34930
+ calls: sum.calls - before.calls,
34931
+ selfSeconds: sum.selfSeconds - before.selfSeconds,
34932
+ totalSeconds: sum.totalSeconds - before.totalSeconds
34933
+ };
34934
+ if (added.calls < 0 || added.selfSeconds < 0 || added.totalSeconds < 0) {
34935
+ shrunk += 1;
34936
+ } else if (added.calls > 0) {
34937
+ functions[signature] = added;
34938
+ }
34939
+ }
34940
+ shrunk += Object.keys(mark.functions).filter((signature) => now.functions[signature] === undefined && (mark.functions[signature]?.calls ?? 0) > 0).length;
34941
+ return {
34942
+ totals: {
34943
+ ...now,
34944
+ frames: now.frames - mark.frames,
34945
+ unreadable: Math.max(0, now.unreadable - mark.unreadable),
34946
+ ...now.cappedFrames === undefined ? {} : { cappedFrames: Math.max(0, now.cappedFrames - (mark.cappedFrames ?? 0)) },
34947
+ functions
34948
+ },
34949
+ shrunk
34950
+ };
34951
+ }
34818
34952
  function perCallCostOf(totals) {
34819
34953
  for (const [signature, sum] of Object.entries(totals.functions)) {
34820
34954
  const parts = signatureParts(signature);
@@ -35467,7 +35601,7 @@ var TOOL_SPECS = [
35467
35601
  rootNodeType: {
35468
35602
  type: "string",
35469
35603
  ops: ["create"],
35470
- description: "create: the root node class. Default Node."
35604
+ description: "create: the root node class, an engine class or a class_name a project script declares, which the root is made as with that script attached. Default Node."
35471
35605
  },
35472
35606
  rootNodeName: {
35473
35607
  type: "string",
@@ -35505,7 +35639,7 @@ var TOOL_SPECS = [
35505
35639
  {
35506
35640
  name: "scene_node",
35507
35641
  title: "Scene nodes",
35508
- description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type 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. 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, and for a node that belongs to a scene this one instances or inherits. ${WITHOUT_AN_EDITOR}`,
35642
+ description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. 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, and for a node that belongs to a scene this one instances or inherits. ${WITHOUT_AN_EDITOR}`,
35509
35643
  parameters: {
35510
35644
  projectPath: PROJECT_PATH2,
35511
35645
  scenePath: SCENE_PATH,
@@ -35518,7 +35652,7 @@ var TOOL_SPECS = [
35518
35652
  nodeType: {
35519
35653
  type: "string",
35520
35654
  ops: ["add"],
35521
- description: 'add: the node class, such as "CharacterBody2D". Give this or instancePath.'
35655
+ description: 'add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath.'
35522
35656
  },
35523
35657
  instancePath: {
35524
35658
  type: "string",
@@ -35863,7 +35997,7 @@ var TOOL_SPECS = [
35863
35997
  resourceType: {
35864
35998
  type: "string",
35865
35999
  ops: ["create"],
35866
- description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D".'
36000
+ description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D", or a class_name a project script declares, which the resource is made as with that script attached.'
35867
36001
  },
35868
36002
  properties: { ...PROPERTIES, ops: ["create", "modify"] },
35869
36003
  script: {
@@ -36057,6 +36191,17 @@ var TOOL_SPECS = [
36057
36191
  ops: ["profile"],
36058
36192
  description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed. A property's get or set block, which the profile names Class.@name_getter or Class.@name_setter, is asked about by that name or by the property's: the places are where the property is named, each saying instead of called whether it writes the property (access write), which runs the set block, or reads it (access read), which runs the get block, and the rows are both blocks'. 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 places."
36059
36193
  },
36194
+ mark: {
36195
+ type: "boolean",
36196
+ ops: ["profile"],
36197
+ description: `profile: answer as usual and set the run's mark at these totals, so a later profile with since: "mark" answers for one action, a press or a commit, apart from the boot and everything before it. The mark is kept beside the run's profile, so a server that replaces this one reads it too, and setting it again moves it. Given with since, the answer counts from the mark before it moves, which times one span after another. Default false.`
36198
+ },
36199
+ since: {
36200
+ type: "string",
36201
+ enum: ["mark"],
36202
+ ops: ["profile"],
36203
+ description: 'profile: "mark" answers only what was added after the mark that mark: true set in this run: per function the calls and self and total milliseconds since it, with the functions that added no call left out, frames and coveredMs since it, and callersOf read off the same difference. Refused when no mark was set in this run, or when the profile holds less than it did at the mark, which is a profiler started again since.'
36204
+ },
36060
36205
  limit: {
36061
36206
  type: "integer",
36062
36207
  minimum: 1,
@@ -36108,7 +36253,7 @@ var TOOL_SPECS = [
36108
36253
  requires: []
36109
36254
  }
36110
36255
  },
36111
- answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
36256
+ answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
36112
36257
  defaultOperation: "start"
36113
36258
  },
36114
36259
  {
@@ -36906,16 +37051,16 @@ function serverInstructions() {
36906
37051
  import { createHash as createHash5 } from "node:crypto";
36907
37052
  import {
36908
37053
  cpSync,
36909
- existsSync as existsSync19,
37054
+ existsSync as existsSync20,
36910
37055
  mkdirSync as mkdirSync14,
36911
- readdirSync as readdirSync10,
37056
+ readdirSync as readdirSync11,
36912
37057
  readFileSync as readFileSync23,
36913
37058
  rmdirSync,
36914
37059
  rmSync as rmSync12,
36915
- statSync as statSync11,
37060
+ statSync as statSync12,
36916
37061
  writeFileSync as writeFileSync15
36917
37062
  } from "node:fs";
36918
- import { dirname as dirname17, join as join25 } from "node:path";
37063
+ import { dirname as dirname18, join as join26 } from "node:path";
36919
37064
  import { fileURLToPath as fileURLToPath3 } from "node:url";
36920
37065
  var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
36921
37066
  var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
@@ -36934,13 +37079,13 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
36934
37079
  function addonsDigest(names, from) {
36935
37080
  const hash = createHash5("sha256");
36936
37081
  const walk = (directory, relative) => {
36937
- for (const name of readdirSync10(directory).sort()) {
37082
+ for (const name of readdirSync11(directory).sort()) {
36938
37083
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
36939
37084
  continue;
36940
37085
  }
36941
- const path = join25(directory, name);
37086
+ const path = join26(directory, name);
36942
37087
  const inside = `${relative}/${name}`;
36943
- if (statSync11(path).isDirectory()) {
37088
+ if (statSync12(path).isDirectory()) {
36944
37089
  walk(path, inside);
36945
37090
  } else {
36946
37091
  hash.update(`${inside}
@@ -36952,7 +37097,7 @@ function addonsDigest(names, from) {
36952
37097
  }
36953
37098
  };
36954
37099
  for (const name of names) {
36955
- walk(join25(from, name), name);
37100
+ walk(join26(from, name), name);
36956
37101
  }
36957
37102
  return hash.digest("hex");
36958
37103
  }
@@ -36985,19 +37130,19 @@ function installedRuntimeDigest(projectPath) {
36985
37130
  return digestMarker(projectPath, RUNTIME_ADDON);
36986
37131
  }
36987
37132
  function digestMarker(projectPath, addon) {
36988
- const marker = join25(projectPath, "addons", addon, DIGEST_MARKER);
36989
- if (!existsSync19(marker)) {
37133
+ const marker = join26(projectPath, "addons", addon, DIGEST_MARKER);
37134
+ if (!existsSync20(marker)) {
36990
37135
  return null;
36991
37136
  }
36992
37137
  const said = readFileSync23(marker, "utf8").trim();
36993
37138
  return said === "" ? null : said;
36994
37139
  }
36995
37140
  function shippedAddonsDirectory() {
36996
- return join25(dirname17(fileURLToPath3(import.meta.url)), "godot", "addons");
37141
+ return join26(dirname18(fileURLToPath3(import.meta.url)), "godot", "addons");
36997
37142
  }
36998
37143
  function installedAddonVersion(projectPath) {
36999
- const marker = join25(projectPath, "addons", ADDONS[0], VERSION_MARKER);
37000
- if (!existsSync19(marker)) {
37144
+ const marker = join26(projectPath, "addons", ADDONS[0], VERSION_MARKER);
37145
+ if (!existsSync20(marker)) {
37001
37146
  return null;
37002
37147
  }
37003
37148
  const said = readFileSync23(marker, "utf8").trim();
@@ -37011,7 +37156,7 @@ function loaderAmong(projectPath, named) {
37011
37156
  }
37012
37157
  let source = null;
37013
37158
  try {
37014
- source = readFileSync23(join25(projectPath, path), "utf8");
37159
+ source = readFileSync23(join26(projectPath, path), "utf8");
37015
37160
  } catch {}
37016
37161
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
37017
37162
  return { name: entry, path, exists: true };
@@ -37053,10 +37198,10 @@ function enabledPlugins(settings) {
37053
37198
  function inspectProject(projectPath) {
37054
37199
  const problems = [];
37055
37200
  const addons = ADDONS.map((name) => {
37056
- const directory = join25(projectPath, "addons", name);
37057
- const marker = join25(directory, VERSION_MARKER);
37058
- const installed = existsSync19(directory);
37059
- const version = existsSync19(marker) ? readFileSync23(marker, "utf8").trim() : null;
37201
+ const directory = join26(projectPath, "addons", name);
37202
+ const marker = join26(directory, VERSION_MARKER);
37203
+ const installed = existsSync20(directory);
37204
+ const version = existsSync20(marker) ? readFileSync23(marker, "utf8").trim() : null;
37060
37205
  const current = version === SERVER_VERSION;
37061
37206
  if (!installed) {
37062
37207
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -37065,7 +37210,7 @@ function inspectProject(projectPath) {
37065
37210
  }
37066
37211
  return { name, installed, version, current };
37067
37212
  });
37068
- const settings = parseProjectGodot(readFileSync23(join25(projectPath, "project.godot"), "utf8"));
37213
+ const settings = parseProjectGodot(readFileSync23(join26(projectPath, "project.godot"), "utf8"));
37069
37214
  const pluginsEnabled = enabledPlugins(settings);
37070
37215
  for (const name of EDITOR_PLUGINS) {
37071
37216
  if (!pluginsEnabled.includes(name)) {
@@ -37081,7 +37226,7 @@ function inspectProject(projectPath) {
37081
37226
  const tracked = trackedByGit(projectPath, [...named.values()]);
37082
37227
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
37083
37228
  for (const [name, path] of named) {
37084
- if (!existsSync19(join25(projectPath, path))) {
37229
+ if (!existsSync20(join26(projectPath, path))) {
37085
37230
  if (!addonMissing(path)) {
37086
37231
  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`);
37087
37232
  }
@@ -37133,7 +37278,7 @@ var UPDATE_NOTICE_EVERY = 500;
37133
37278
  var FEEDBACK_NOTICE_EVERY = 250;
37134
37279
  var FAILED_BUDGET = 1e6;
37135
37280
  var ANSWER_BUDGET = 4000000;
37136
- var __dirname2 = dirname18(fileURLToPath4(import.meta.url));
37281
+ var __dirname2 = dirname19(fileURLToPath4(import.meta.url));
37137
37282
  var PARENT_ASKED_AFTER_MS = 5000;
37138
37283
  function editorRestartTimeoutMs() {
37139
37284
  return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
@@ -37165,22 +37310,22 @@ function writtenFileRefusal(path, rule) {
37165
37310
  if (/^(res|user):\/\//.test(path)) {
37166
37311
  return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
37167
37312
  }
37168
- if (!isAbsolute4(path)) {
37313
+ if (!isAbsolute5(path)) {
37169
37314
  return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
37170
37315
  }
37171
37316
  if (!rule.extensions.includes(extname4(path).toLowerCase())) {
37172
37317
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
37173
37318
  }
37174
- const directory = realPathOr(dirname18(path));
37175
- if (!existsSync20(directory)) {
37176
- return `${named}'s directory ${dirname18(path)} does not exist; ${what} creates no directories.`;
37319
+ const directory = realPathOr(dirname19(path));
37320
+ if (!existsSync21(directory)) {
37321
+ return `${named}'s directory ${dirname19(path)} does not exist; ${what} creates no directories.`;
37177
37322
  }
37178
- const where = join26(directory, basename7(path));
37323
+ const where = join27(directory, basename8(path));
37179
37324
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
37180
37325
  if (inside !== undefined) {
37181
37326
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
37182
37327
  }
37183
- const taken = rule.writes(where).find((file) => existsSync20(file));
37328
+ const taken = rule.writes(where).find((file) => existsSync21(file));
37184
37329
  if (taken !== undefined) {
37185
37330
  return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
37186
37331
  }
@@ -37291,7 +37436,7 @@ function exited(run) {
37291
37436
  return run.exitCode !== null || run.exitSignal !== null;
37292
37437
  }
37293
37438
  function howItExited(run) {
37294
- return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}, with no exit code`;
37439
+ return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}, with no exit code`;
37295
37440
  }
37296
37441
  function errorsUnread(run) {
37297
37442
  return run.throughEditor && typeof run.errorReport !== "string";
@@ -37404,7 +37549,7 @@ function endedWithoutACode(run) {
37404
37549
  function ourRunEnded(run, now) {
37405
37550
  const which = `${run.throughEditor ? "played in the editor" : "started"}${run.pid === null ? "" : ` (pid ${run.pid})`}`;
37406
37551
  const when = run.endedAt === undefined ? "" : ` ${Math.max(0, Math.round((now - run.endedAt) / 1000))}s ago`;
37407
- const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with code ${run.exitCode}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
37552
+ const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with ${exitCodeText(run.exitCode)}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
37408
37553
  return `No game with the runtime addon is running: the game this server ${which} ended${when}, and ${how}. editor_output has what it printed, and editor_run start runs it again.`;
37409
37554
  }
37410
37555
  function stopVerdict(stop) {
@@ -37742,7 +37887,7 @@ function notRunNote(notRun, failFast) {
37742
37887
  function exportPresetRefusal(projectPath, preset) {
37743
37888
  let text;
37744
37889
  try {
37745
- text = readFileSync24(join26(projectPath, "export_presets.cfg"), "utf8");
37890
+ text = readFileSync24(join27(projectPath, "export_presets.cfg"), "utf8");
37746
37891
  } catch {
37747
37892
  return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. project_export add_preset writes one, as the editor's Project > Export does.`;
37748
37893
  }
@@ -37764,7 +37909,7 @@ function exportPresetRefusal(projectPath, preset) {
37764
37909
  }
37765
37910
  return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. project_export add_preset adds one.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
37766
37911
  }
37767
- function exportAnswer(ending, asked, before, after, beside = []) {
37912
+ function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null) {
37768
37913
  const { log } = ending;
37769
37914
  const written = after !== null && after !== before;
37770
37915
  const notCopied = librariesNotCopied(log.all);
@@ -37781,6 +37926,7 @@ function exportAnswer(ending, asked, before, after, beside = []) {
37781
37926
  librariesNotLoaded: notCopied,
37782
37927
  extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
37783
37928
  },
37929
+ ...docCacheNote === null ? {} : { docCacheNote },
37784
37930
  ...foldedEntriesAnswer(log)
37785
37931
  };
37786
37932
  if (verdict.exported) {
@@ -37805,9 +37951,9 @@ function writtenBeside(before, after, output, within) {
37805
37951
  function filesIn(folder) {
37806
37952
  const found = new Map;
37807
37953
  try {
37808
- for (const entry of readdirSync11(folder, { withFileTypes: true })) {
37954
+ for (const entry of readdirSync12(folder, { withFileTypes: true })) {
37809
37955
  if (entry.isFile()) {
37810
- found.set(entry.name, statSync12(join26(folder, entry.name)).mtimeMs);
37956
+ found.set(entry.name, statSync13(join27(folder, entry.name)).mtimeMs);
37811
37957
  }
37812
37958
  }
37813
37959
  } catch {}
@@ -37841,7 +37987,7 @@ function unsavedInEditorNote(scripts) {
37841
37987
  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.`;
37842
37988
  }
37843
37989
  function editorAddonMissing(projectPath) {
37844
- if (!existsSync20(join26(projectPath, "project.godot"))) {
37990
+ if (!existsSync21(join27(projectPath, "project.godot"))) {
37845
37991
  return null;
37846
37992
  }
37847
37993
  const report = inspectProject(projectPath);
@@ -38129,7 +38275,7 @@ function profileFrameFunctions(projectPath) {
38129
38275
  let projectGodot = null;
38130
38276
  if (projectPath !== null) {
38131
38277
  try {
38132
- projectGodot = readFileSync24(join26(projectPath, "project.godot"), "utf8");
38278
+ projectGodot = readFileSync24(join27(projectPath, "project.godot"), "utf8");
38133
38279
  } catch {}
38134
38280
  }
38135
38281
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -38307,7 +38453,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
38307
38453
  var ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd";
38308
38454
  function gdUnitFailsOnScriptErrors(projectPath) {
38309
38455
  try {
38310
- return settingKeys(readFileSync24(join26(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
38456
+ return settingKeys(readFileSync24(join27(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
38311
38457
  } catch {
38312
38458
  return true;
38313
38459
  }
@@ -38322,13 +38468,13 @@ function uncaughtNote(uncaught) {
38322
38468
  }
38323
38469
  function elsewhereIn(projectPath, asked) {
38324
38470
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
38325
- if (wanted === "" || existsSync20(join26(projectPath, wanted))) {
38471
+ if (wanted === "" || existsSync21(join27(projectPath, wanted))) {
38326
38472
  return "";
38327
38473
  }
38328
38474
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
38329
38475
  let near = [];
38330
38476
  try {
38331
- near = readdirSync11(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
38477
+ near = readdirSync12(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
38332
38478
  } catch {
38333
38479
  return "";
38334
38480
  }
@@ -38420,23 +38566,23 @@ function engineChecked(why) {
38420
38566
  var EXTERNAL_MEMBER = 'Could not resolve external class member "';
38421
38567
  function isDirectory(path) {
38422
38568
  try {
38423
- return statSync12(path).isDirectory();
38569
+ return statSync13(path).isDirectory();
38424
38570
  } catch {
38425
38571
  return false;
38426
38572
  }
38427
38573
  }
38428
38574
  function scriptsUnder(directory, prefix) {
38429
- if (existsSync20(join26(directory, ".gdignore"))) {
38575
+ if (existsSync21(join27(directory, ".gdignore"))) {
38430
38576
  return [];
38431
38577
  }
38432
38578
  const found = [];
38433
- for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
38579
+ for (const entry of readdirSync12(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
38434
38580
  if (entry.name.startsWith(".")) {
38435
38581
  continue;
38436
38582
  }
38437
38583
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
38438
38584
  if (entry.isDirectory()) {
38439
- found.push(...scriptsUnder(join26(directory, entry.name), path));
38585
+ found.push(...scriptsUnder(join27(directory, entry.name), path));
38440
38586
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
38441
38587
  found.push(path);
38442
38588
  }
@@ -38452,7 +38598,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
38452
38598
  return false;
38453
38599
  }
38454
38600
  try {
38455
- return readFileSync24(join26(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
38601
+ return readFileSync24(join27(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
38456
38602
  } catch {
38457
38603
  return false;
38458
38604
  }
@@ -38478,7 +38624,7 @@ var STATIC_LIST_HINT = { ttlMs: 5 * 60000, cacheScope: "private" };
38478
38624
  class GodotServer {
38479
38625
  stdio = null;
38480
38626
  locator = new GodotLocator;
38481
- operationsScript = join26(__dirname2, "godot", "operations", "godot_operations.gd");
38627
+ operationsScript = join27(__dirname2, "godot", "operations", "godot_operations.gd");
38482
38628
  godotBridge;
38483
38629
  tools = buildToolDefinitions();
38484
38630
  activeProcess = null;
@@ -38739,14 +38885,14 @@ class GodotServer {
38739
38885
  }
38740
38886
  noteTheStart() {
38741
38887
  const cwd = process.cwd();
38742
- const mcpJson = join26(cwd, ".mcp.json");
38888
+ const mcpJson = join27(cwd, ".mcp.json");
38743
38889
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
38744
38890
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
38745
38891
  writeLifecycle("started", {
38746
38892
  argv: process.argv.slice(1),
38747
38893
  cwd,
38748
38894
  project: this.ownProject,
38749
- mcpJson: existsSync20(mcpJson) ? mcpJson : null
38895
+ mcpJson: existsSync21(mcpJson) ? mcpJson : null
38750
38896
  });
38751
38897
  if (predecessor !== null) {
38752
38898
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -38887,7 +39033,7 @@ class GodotServer {
38887
39033
  return checked.response;
38888
39034
  }
38889
39035
  const named = args["projectPath"];
38890
- if (typeof named === "string" && existsSync20(join26(named, "project.godot"))) {
39036
+ if (typeof named === "string" && existsSync21(join27(named, "project.godot"))) {
38891
39037
  this.lastProjectPath = named;
38892
39038
  }
38893
39039
  this.updates.refresh();
@@ -38923,7 +39069,7 @@ class GodotServer {
38923
39069
  let notSaved;
38924
39070
  try {
38925
39071
  savedAt = answer.content.map((item, index) => {
38926
- const file = join26(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
39072
+ const file = join27(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
38927
39073
  writeFileSync16(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
38928
39074
  return file;
38929
39075
  });
@@ -39541,8 +39687,8 @@ class GodotServer {
39541
39687
  if (path === undefined) {
39542
39688
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
39543
39689
  }
39544
- const file = join26(path, "project.godot");
39545
- if (!existsSync20(file)) {
39690
+ const file = join27(path, "project.godot");
39691
+ if (!existsSync21(file)) {
39546
39692
  return {
39547
39693
  ok: false,
39548
39694
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -39588,7 +39734,7 @@ class GodotServer {
39588
39734
  }
39589
39735
  runEnvironment(args) {
39590
39736
  const savesIn = readNonEmptyString(args, "savesIn");
39591
- if (savesIn !== undefined && !isAbsolute4(savesIn)) {
39737
+ if (savesIn !== undefined && !isAbsolute5(savesIn)) {
39592
39738
  return {
39593
39739
  ok: false,
39594
39740
  response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
@@ -39847,7 +39993,8 @@ class GodotServer {
39847
39993
  uidsCreated: given,
39848
39994
  stillWithoutUid: after,
39849
39995
  note: uidsLeftNote(after.length),
39850
- ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
39996
+ ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
39997
+ ...imported.docCacheNote === undefined ? {} : { docCacheNote: imported.docCacheNote }
39851
39998
  }
39852
39999
  }, scanned));
39853
40000
  }
@@ -40086,6 +40233,9 @@ class GodotServer {
40086
40233
  if (imported.extensionNote !== undefined) {
40087
40234
  extra["extensionNote"] = imported.extensionNote;
40088
40235
  }
40236
+ if (imported.docCacheNote !== undefined) {
40237
+ extra["docCacheNote"] = imported.docCacheNote;
40238
+ }
40089
40239
  }
40090
40240
  const after = await this.operation("get_import_status", statusAsk, projectPath);
40091
40241
  if (!after.ok) {
@@ -40202,7 +40352,7 @@ class GodotServer {
40202
40352
  const name = application["config/name"];
40203
40353
  const mainScene = application["run/main_scene"];
40204
40354
  const info = {
40205
- name: typeof name === "string" && name !== "" ? name : basename7(project.value.path),
40355
+ name: typeof name === "string" && name !== "" ? name : basename8(project.value.path),
40206
40356
  path: project.value.path,
40207
40357
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
40208
40358
  godotVersion: await this.godotVersion(engine.value),
@@ -40285,18 +40435,18 @@ class GodotServer {
40285
40435
  if (!engine.ok) {
40286
40436
  return engine.response;
40287
40437
  }
40288
- const folder = dirname18(output.absolutePath);
40438
+ const folder = dirname19(output.absolutePath);
40289
40439
  try {
40290
40440
  mkdirSync15(folder, { recursive: true });
40291
40441
  } catch (error) {
40292
- return this.createErrorResponse(`Cannot create ${dirname18(output.relativePath)} to export into: ${errorMessage(error)}`);
40442
+ return this.createErrorResponse(`Cannot create ${dirname19(output.relativePath)} to export into: ${errorMessage(error)}`);
40293
40443
  }
40294
40444
  const debug = readBoolean(args, "debug") ?? false;
40295
40445
  const exportLogs = scratchDirectory("export");
40296
40446
  const exportArgs = [
40297
40447
  "--headless",
40298
40448
  "--log-file",
40299
- join26(exportLogs, "engine.log"),
40449
+ join27(exportLogs, "engine.log"),
40300
40450
  "--path",
40301
40451
  project.value.path,
40302
40452
  debug ? "--export-debug" : "--export-release",
@@ -40304,24 +40454,37 @@ class GodotServer {
40304
40454
  output.absolutePath
40305
40455
  ];
40306
40456
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
40307
- const writtenAt = () => existsSync20(output.absolutePath) ? statSync12(output.absolutePath).mtimeMs : null;
40308
- const before = writtenAt();
40309
- const listedBefore = filesIn(folder);
40310
- const freed = freeLibraryCopies(project.value.path);
40311
- if (freed.setAside.length > 0 || freed.swept.length > 0) {
40312
- this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
40313
- }
40314
- let ending;
40457
+ const writtenAt = () => existsSync21(output.absolutePath) ? statSync13(output.absolutePath).mtimeMs : null;
40458
+ const caches = editorCacheDirectory(engine.value);
40459
+ const exportOnce = async () => {
40460
+ const before = writtenAt();
40461
+ const listedBefore = filesIn(folder);
40462
+ const cachesBefore = docCachesWritten(caches);
40463
+ const freed = freeLibraryCopies(project.value.path);
40464
+ if (freed.setAside.length > 0 || freed.swept.length > 0) {
40465
+ this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
40466
+ }
40467
+ const ending = await runEngine(engine.value, exportArgs, {
40468
+ timeout: { ms: 300000, said: "five minutes" }
40469
+ });
40470
+ const crash = docCacheCrash(ending, cachesBefore, docCachesWritten(caches), "export");
40471
+ return { ending, before, listedBefore, crash };
40472
+ };
40473
+ let run;
40474
+ let docCacheNote;
40315
40475
  try {
40316
- ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
40476
+ const first = await exportOnce();
40477
+ docCacheNote = first.crash;
40478
+ run = first.crash === null ? first : await exportOnce();
40317
40479
  } finally {
40318
40480
  discard(exportLogs);
40319
40481
  }
40482
+ const { ending, before, listedBefore } = run;
40320
40483
  if (ending.exitSignal === null && ending.exitCode === null) {
40321
40484
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
40322
40485
  }
40323
- const beside = writtenBeside(listedBefore, filesIn(folder), basename7(output.absolutePath), dirname18(output.relativePath).replaceAll("\\", "/"));
40324
- return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside);
40486
+ const beside = writtenBeside(listedBefore, filesIn(folder), basename8(output.absolutePath), dirname19(output.relativePath).replaceAll("\\", "/"));
40487
+ return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
40325
40488
  }
40326
40489
  reportTestProgress(log) {
40327
40490
  const progress = callProgress();
@@ -40374,10 +40537,10 @@ class GodotServer {
40374
40537
  if (movie !== null) {
40375
40538
  return this.createErrorResponse(movie);
40376
40539
  }
40377
- const missing = paths.value.filter((path) => !existsSync20(join26(project.value.path, path.slice("res://".length))));
40540
+ const missing = paths.value.filter((path) => !existsSync21(join27(project.value.path, path.slice("res://".length))));
40378
40541
  const asked = paths.value.filter((path) => !missing.includes(path));
40379
40542
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
40380
- const noRunner = !existsSync20(join26(project.value.path, runner));
40543
+ const noRunner = !existsSync21(join27(project.value.path, runner));
40381
40544
  if (asked.length === 0) {
40382
40545
  const verdict = `nothing at ${missing.join(" and ")}`;
40383
40546
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -40468,21 +40631,21 @@ class GodotServer {
40468
40631
  await untilGone([run.process.pid]);
40469
40632
  }
40470
40633
  await run.settled;
40471
- const reportsDir = join26(project.value.path, ".godot", ...ours.split("/"));
40634
+ const reportsDir = join27(project.value.path, ".godot", ...ours.split("/"));
40472
40635
  let report = null;
40473
40636
  let reportProblem = null;
40474
40637
  try {
40475
- const written = existsSync20(reportsDir) ? readdirSync11(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
40638
+ const written = existsSync21(reportsDir) ? readdirSync12(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
40476
40639
  const newest = written.at(-1);
40477
40640
  if (newest !== undefined) {
40478
- report = parseJUnit(readFileSync24(join26(reportsDir, newest, "results.xml"), "utf8"));
40641
+ report = parseJUnit(readFileSync24(join27(reportsDir, newest, "results.xml"), "utf8"));
40479
40642
  }
40480
40643
  } catch (error) {
40481
40644
  reportProblem = errorMessage(error);
40482
40645
  } finally {
40483
40646
  discard(reportsDir, userData);
40484
40647
  try {
40485
- rmdirSync2(dirname18(reportsDir));
40648
+ rmdirSync2(dirname19(reportsDir));
40486
40649
  } catch {}
40487
40650
  }
40488
40651
  const engineEntries = foldedRepeats(run.log.select({
@@ -40514,7 +40677,7 @@ class GodotServer {
40514
40677
  const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
40515
40678
  const scriptErrors = [...declaring.origins, ...printedErrors];
40516
40679
  const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
40517
- const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
40680
+ const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? howItExited(run);
40518
40681
  const nothingAt = missing.join(" and ");
40519
40682
  const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
40520
40683
  const missingNote = missing.length === 0 ? undefined : `Nothing at ${nothingAt}, so ${missing.length === 1 ? "it was" : "they were"} left out and only ${asked.join(", ")} ran; a run of fewer paths than were asked for is not called a pass.${elsewhereIn(project.value.path, missing[0] ?? "")}`;
@@ -40841,7 +41004,7 @@ class GodotServer {
40841
41004
  const cached = cachedClasses(projectPath);
40842
41005
  const sourceOf = (resourcePath) => {
40843
41006
  const contained = resolveWithinProject(projectPath, resourcePath);
40844
- if (!contained.ok || !existsSync20(contained.absolutePath)) {
41007
+ if (!contained.ok || !existsSync21(contained.absolutePath)) {
40845
41008
  return null;
40846
41009
  }
40847
41010
  try {
@@ -40935,7 +41098,7 @@ class GodotServer {
40935
41098
  }
40936
41099
  sourceOfClass(projectPath, resourcePath) {
40937
41100
  const contained = resolveWithinProject(projectPath, resourcePath);
40938
- if (!contained.ok || !existsSync20(contained.absolutePath)) {
41101
+ if (!contained.ok || !existsSync21(contained.absolutePath)) {
40939
41102
  return null;
40940
41103
  }
40941
41104
  try {
@@ -41097,7 +41260,7 @@ class GodotServer {
41097
41260
  ...Array.isArray(stack) ? {
41098
41261
  stack: stack.map((frame) => ({
41099
41262
  ...frame,
41100
- ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join26(projectPath, frame["script"].slice("res://".length)) } : {}
41263
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join27(projectPath, frame["script"].slice("res://".length)) } : {}
41101
41264
  }))
41102
41265
  } : {}
41103
41266
  });
@@ -41376,7 +41539,7 @@ class GodotServer {
41376
41539
  return null;
41377
41540
  }
41378
41541
  try {
41379
- return settingKeys(readFileSync24(join26(projectPath, "project.godot"), "utf8"));
41542
+ return settingKeys(readFileSync24(join27(projectPath, "project.godot"), "utf8"));
41380
41543
  } catch {
41381
41544
  return null;
41382
41545
  }
@@ -41813,7 +41976,7 @@ class GodotServer {
41813
41976
  }
41814
41977
  async runtimeUp(projectPath, before, budget, withArgs) {
41815
41978
  const budgetMs = budget.ms;
41816
- if (!existsSync20(join26(projectPath, RUNTIME_AUTOLOAD.path))) {
41979
+ if (!existsSync21(join27(projectPath, RUNTIME_AUTOLOAD.path))) {
41817
41980
  return runtimeVerdict(null, {
41818
41981
  addon: false,
41819
41982
  budgetMs,
@@ -41899,7 +42062,7 @@ class GodotServer {
41899
42062
  }
41900
42063
  let announcedAt;
41901
42064
  try {
41902
- announcedAt = statSync12(endpoint.file).mtimeMs;
42065
+ announcedAt = statSync13(endpoint.file).mtimeMs;
41903
42066
  } catch {
41904
42067
  return;
41905
42068
  }
@@ -42789,9 +42952,13 @@ class GodotServer {
42789
42952
  if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
42790
42953
  return this.createErrorResponse(`callersOf takes a function's name, or Class.name for the functions of one class, and ${JSON.stringify(callersName)} is neither.`, ["editor_run profile names each function as it answers; pass one of those"]);
42791
42954
  }
42955
+ const asked = {
42956
+ mark: readBoolean(args, "mark") === true,
42957
+ since: readString(args, "since") === "mark"
42958
+ };
42792
42959
  const going = await this.runStillGoing(run);
42793
42960
  const projectPath = run.projectPath;
42794
- const projectGone = projectPath !== null && !existsSync20(join26(projectPath, "project.godot"));
42961
+ const projectGone = projectPath !== null && !existsSync21(join27(projectPath, "project.godot"));
42795
42962
  if (projectGone && callersName !== undefined) {
42796
42963
  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"]);
42797
42964
  }
@@ -42801,7 +42968,7 @@ class GodotServer {
42801
42968
  ...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
42802
42969
  };
42803
42970
  if (run.throughEditor) {
42804
- return await this.profileOfThePlayedGame(going, projectPath, fromProject, limit);
42971
+ return await this.profileOfThePlayedGame(run, going, fromProject, limit, asked);
42805
42972
  }
42806
42973
  const path = run.profile;
42807
42974
  if (path === undefined) {
@@ -42814,15 +42981,16 @@ class GodotServer {
42814
42981
  ]);
42815
42982
  }
42816
42983
  const totals = file.connected ? file : null;
42817
- return this.jsonTextResponse(profileAnswer({
42984
+ return this.profileReply(run, {
42818
42985
  through: "gdharness",
42819
42986
  running: going,
42820
42987
  totals,
42821
42988
  coveredMs: totals?.lastFrameAt == null ? 0 : totals.lastFrameAt - totals.startedAt,
42822
42989
  ...fromProject
42823
- }, limit));
42990
+ }, limit, asked);
42824
42991
  }
42825
- async profileOfThePlayedGame(going, projectPath, fromProject, limit) {
42992
+ async profileOfThePlayedGame(run, going, fromProject, limit, asked) {
42993
+ const { projectPath } = run;
42826
42994
  const read = await this.handleViaBridge("profile_read", {});
42827
42995
  if (read.isError === true) {
42828
42996
  return read;
@@ -42844,12 +43012,13 @@ class GodotServer {
42844
43012
  return this.createErrorResponse("The editor says it is playing and has no debugger session for the game, so there is nothing to switch the profiler on in yet.", ["Ask again in a moment, once the game is up"]);
42845
43013
  }
42846
43014
  const on = await this.firstProfiledFrame();
43015
+ const unmarked = asked.mark || asked.since ? " Nothing was marked, and nothing is counted from a mark: the profile begins now, so an answer without since covers the same span." : "";
42847
43016
  return this.jsonTextResponse({
42848
43017
  through: "editor",
42849
43018
  running: true,
42850
43019
  started: true,
42851
43020
  functions: [],
42852
- note: on ? "The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start." : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.`
43021
+ note: on ? `The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}` : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}`
42853
43022
  });
42854
43023
  }
42855
43024
  const functions = asParams(held["functions"]);
@@ -42872,13 +43041,70 @@ class GodotServer {
42872
43041
  ];
42873
43042
  }))
42874
43043
  } : null;
42875
- return this.jsonTextResponse(profileAnswer({
43044
+ return this.profileReply(run, {
42876
43045
  through: "editor",
42877
43046
  running: going,
42878
43047
  totals,
42879
43048
  coveredMs: readNonNegativeNumber(held, "coveredMs") ?? 0,
42880
43049
  ...fromProject
42881
- }, limit));
43050
+ }, limit, asked);
43051
+ }
43052
+ profileReply(run, reading, limit, asked) {
43053
+ const markPath = profileMarkPath(run.startedAt);
43054
+ const now = Date.now();
43055
+ let shown = reading;
43056
+ const lead = [];
43057
+ let sinceFields = {};
43058
+ if (asked.since) {
43059
+ const mark = readProfileMark(markPath);
43060
+ if (mark === null || mark.runStartedAt !== run.startedAt || mark.projectPath !== run.projectPath) {
43061
+ return this.createErrorResponse("No mark has been set in this run, so there is nothing to count from.", ["editor_run profile with mark: true sets one at the totals it answers with"]);
43062
+ }
43063
+ const added = reading.totals === null ? null : totalsSince(reading.totals, mark.totals);
43064
+ if (added === null) {
43065
+ return this.createErrorResponse("The profile holds less than it did at the mark, so the profiler was started again since, and what it holds now cannot be counted from the mark.", ["editor_run profile with mark: true sets a mark in the profile as it is now"]);
43066
+ }
43067
+ shown = {
43068
+ ...reading,
43069
+ totals: added.totals,
43070
+ coveredMs: Math.max(0, reading.coveredMs - mark.coveredMs)
43071
+ };
43072
+ const ago = now - mark.takenAt;
43073
+ sinceFields = {
43074
+ since: "mark",
43075
+ markSetMsAgo: ago,
43076
+ ...added.shrunk > 0 ? { lessThanAtMark: added.shrunk } : {}
43077
+ };
43078
+ lead.push(`Counted from the mark set ${ago}ms ago: each function's calls and milliseconds are what it added after the mark, frames and coveredMs are those since it, and a function that added no call is left out.`);
43079
+ if (added.shrunk > 0) {
43080
+ const one = added.shrunk === 1;
43081
+ lead.push(`${one ? "One function reads" : `${added.shrunk} functions read`} less than at the mark and ${one ? "is" : "are"} left out, counted under lessThanAtMark: the engine's own totals arrived after the mark and replaced the frames' sum, and capped totals leave out the functions with the least time.`);
43082
+ }
43083
+ }
43084
+ if (asked.mark) {
43085
+ if (reading.totals === null) {
43086
+ return this.createErrorResponse("There is nothing to mark yet: the game has not connected to the profiler.", ["Ask again in a moment, once the game is up"]);
43087
+ }
43088
+ const mark = {
43089
+ takenAt: now,
43090
+ projectPath: run.projectPath,
43091
+ runStartedAt: run.startedAt,
43092
+ coveredMs: reading.coveredMs,
43093
+ totals: reading.totals
43094
+ };
43095
+ if (!writeProfileMark(markPath, mark)) {
43096
+ return this.createErrorResponse(`The mark could not be written to ${markPath}, so none is set.`);
43097
+ }
43098
+ lead.push(asked.since ? 'The mark is moved to the totals as they are now, so the next since: "mark" counts from here.' : 'The mark is set at these totals: editor_run profile with since: "mark" answers only what is added after it.');
43099
+ }
43100
+ const answer = profileAnswer(shown, limit);
43101
+ const note = [...lead, ...typeof answer["note"] === "string" ? [answer["note"]] : []].join(" ");
43102
+ return this.jsonTextResponse({
43103
+ ...answer,
43104
+ ...sinceFields,
43105
+ ...asked.mark ? { marked: true } : {},
43106
+ note: note === "" ? undefined : note
43107
+ });
42882
43108
  }
42883
43109
  async firstProfiledFrame() {
42884
43110
  const deadline = Date.now() + PROFILER_ON_MS;
@@ -43079,7 +43305,7 @@ class GodotServer {
43079
43305
  notes.push(endedWithoutACode({
43080
43306
  throughEditor: run.throughEditor,
43081
43307
  announced: announced !== undefined || run.announcedPid !== undefined,
43082
- couldAnnounce: run.projectPath !== null && existsSync20(join26(run.projectPath, RUNTIME_AUTOLOAD.path)),
43308
+ couldAnnounce: run.projectPath !== null && existsSync21(join27(run.projectPath, RUNTIME_AUTOLOAD.path)),
43083
43309
  endedHere: typeof run.endedHere === "string",
43084
43310
  pickedUp: run.pickedUp === true
43085
43311
  }));
@@ -43709,7 +43935,7 @@ class GodotServer {
43709
43935
  if (!scriptPath.ok) {
43710
43936
  return this.createErrorResponse(scriptPath.reason);
43711
43937
  }
43712
- if (!existsSync20(scriptPath.absolutePath)) {
43938
+ if (!existsSync21(scriptPath.absolutePath)) {
43713
43939
  return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
43714
43940
  }
43715
43941
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -44291,7 +44517,7 @@ class GodotServer {
44291
44517
  async ownRunOnItsWay(projectPath, budgetMs) {
44292
44518
  const run = await this.currentRun();
44293
44519
  const project = projectPath ?? run?.projectPath ?? null;
44294
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync20(join26(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
44520
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync21(join27(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
44295
44521
  return null;
44296
44522
  }
44297
44523
  const already = runtimesAnnounced().running;
@@ -44300,7 +44526,7 @@ class GodotServer {
44300
44526
  return !run.announcedBefore.has(one.pid);
44301
44527
  }
44302
44528
  try {
44303
- return statSync12(one.file).mtimeMs >= run.startedAt;
44529
+ return statSync13(one.file).mtimeMs >= run.startedAt;
44304
44530
  } catch {
44305
44531
  return false;
44306
44532
  }
@@ -44381,7 +44607,7 @@ class GodotServer {
44381
44607
  }
44382
44608
  }
44383
44609
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
44384
- const screenshotPath = screenshotDir ? join26(screenshotDir, "capture.png") : null;
44610
+ const screenshotPath = screenshotDir ? join27(screenshotDir, "capture.png") : null;
44385
44611
  const staleNote = this.runtimeStaleNote(choice.endpoint);
44386
44612
  const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
44387
44613
  const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}