gdharness 1.3.60 → 1.3.62

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.
Files changed (77) hide show
  1. package/README.md +20 -20
  2. package/build/cli.js +2198 -1757
  3. package/build/godot/addons/auto_reload/auto_reload.gd +3 -3
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +4 -4
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +13 -13
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +6 -6
  7. package/build/godot/addons/gdharness_editor/headless_check.gd +9 -6
  8. package/build/godot/addons/gdharness_editor/property_paths.gd +3 -3
  9. package/build/godot/addons/gdharness_editor/property_values.gd +10 -10
  10. package/build/godot/addons/gdharness_editor/reading.gd +22 -18
  11. package/build/godot/addons/gdharness_editor/scene_file.gd +5 -5
  12. package/build/godot/addons/gdharness_editor/serialisation.gd +4 -4
  13. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +5 -5
  14. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +6 -6
  15. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +9 -9
  16. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +3 -3
  17. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +5 -5
  18. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +5 -5
  19. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
  20. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +1 -1
  21. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +5 -5
  22. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +3 -3
  23. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +3 -3
  24. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +4 -4
  25. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +3 -3
  26. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +10 -10
  27. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +1 -1
  28. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +5 -5
  29. package/build/godot/addons/gdharness_runtime/declared_values.gd +13 -13
  30. package/build/godot/addons/gdharness_runtime/reading.gd +22 -18
  31. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +2 -2
  32. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +89 -18
  33. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +6 -6
  34. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +10 -10
  35. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +5 -4
  36. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +3 -4
  37. package/build/godot/addons/gdharness_runtime/runtime_input.gd +8 -8
  38. package/build/godot/addons/gdharness_runtime/runtime_items.gd +1 -1
  39. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +3 -3
  40. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +5 -5
  41. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +20 -20
  42. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +19 -13
  43. package/build/godot/addons/gdharness_runtime/runtime_says.gd +5 -5
  44. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +39 -39
  45. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +3 -3
  46. package/build/godot/addons/gdharness_runtime/runtime_values.gd +7 -7
  47. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +8 -8
  48. package/build/godot/addons/gdharness_runtime/runtime_words.gd +5 -5
  49. package/build/godot/addons/gdharness_runtime/serialisation.gd +4 -4
  50. package/build/godot/addons/gdharness_tests/bench.gd +1 -1
  51. package/build/godot/addons/gdharness_tests/share.gd +6 -6
  52. package/build/godot/addons/gdharness_tests/suites.gd +1 -1
  53. package/build/godot/operations/audio_buses.gd +4 -4
  54. package/build/godot/operations/class_cache.gd +3 -3
  55. package/build/godot/operations/classdb_queries.gd +1 -1
  56. package/build/godot/operations/declared_values.gd +13 -13
  57. package/build/godot/operations/dependencies.gd +6 -6
  58. package/build/godot/operations/export_presets.gd +7 -7
  59. package/build/godot/operations/file_reads.gd +6 -6
  60. package/build/godot/operations/gdscript_analysis.gd +6 -6
  61. package/build/godot/operations/gdscript_authoring.gd +11 -11
  62. package/build/godot/operations/godot_operations.gd +1 -1
  63. package/build/godot/operations/import_pipeline.gd +12 -12
  64. package/build/godot/operations/input_actions.gd +3 -3
  65. package/build/godot/operations/logger.gd +1 -1
  66. package/build/godot/operations/plugins.gd +2 -2
  67. package/build/godot/operations/project_config.gd +11 -11
  68. package/build/godot/operations/project_validation.gd +1 -1
  69. package/build/godot/operations/property_paths.gd +3 -3
  70. package/build/godot/operations/property_values.gd +10 -10
  71. package/build/godot/operations/reading.gd +22 -18
  72. package/build/godot/operations/resource_files.gd +1 -1
  73. package/build/godot/operations/script_check.gd +2 -2
  74. package/build/godot/operations/serialisation.gd +4 -4
  75. package/build/godot/tests/listed.gd +9 -9
  76. package/build/keeper.js +511 -162
  77. package/package.json +1 -1
package/build/keeper.js CHANGED
@@ -6946,13 +6946,20 @@ var require_public_api = __commonJS(function(exports) {
6946
6946
  });
6947
6947
 
6948
6948
  // src/keeper.ts
6949
- import { closeSync as closeSync3, existsSync as existsSync4, mkdtempSync as mkdtempSync4, openSync as openSync4, readFileSync as readFileSync9, rmSync as rmSync7, writeFileSync as writeFileSync4 } from "node:fs";
6949
+ import { closeSync as closeSync3, existsSync as existsSync5, mkdtempSync as mkdtempSync4, openSync as openSync4, readFileSync as readFileSync10, rmSync as rmSync8, writeFileSync as writeFileSync5 } from "node:fs";
6950
6950
  import { tmpdir as tmpdir8 } from "node:os";
6951
- import { join as join10 } from "node:path";
6951
+ import { join as join11 } from "node:path";
6952
6952
  import { setTimeout as delay3 } from "node:timers/promises";
6953
6953
 
6954
6954
  // src/alive.ts
6955
6955
  import process2 from "node:process";
6956
+
6957
+ // src/errors.ts
6958
+ function errorCode(error) {
6959
+ return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : undefined;
6960
+ }
6961
+
6962
+ // src/alive.ts
6956
6963
  function exitOf(child) {
6957
6964
  if (child.exitCode !== null || child.signalCode !== null) {
6958
6965
  return Promise.resolve({ code: child.exitCode, signal: child.signalCode });
@@ -6971,29 +6978,29 @@ function alive(pid) {
6971
6978
  process2.kill(pid, 0);
6972
6979
  return true;
6973
6980
  } catch (error) {
6974
- return error.code === "EPERM";
6981
+ return errorCode(error) === "EPERM";
6975
6982
  }
6976
6983
  }
6977
6984
 
6978
6985
  // src/desktop.ts
6979
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
6986
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
6980
6987
  import { tmpdir as tmpdir2 } from "node:os";
6981
6988
 
6982
6989
  // src/compiled-helper.ts
6983
6990
  import { createHash } from "node:crypto";
6984
6991
  import {
6985
6992
  closeSync,
6986
- existsSync,
6993
+ existsSync as existsSync2,
6987
6994
  mkdtempSync,
6988
6995
  openSync,
6989
- readFileSync,
6990
- rmSync,
6996
+ readFileSync as readFileSync2,
6997
+ rmSync as rmSync2,
6991
6998
  statSync,
6992
- writeFileSync,
6999
+ writeFileSync as writeFileSync2,
6993
7000
  writeSync
6994
7001
  } from "node:fs";
6995
7002
  import { tmpdir } from "node:os";
6996
- import { join } from "node:path";
7003
+ import { join as join2 } from "node:path";
6997
7004
  import { setTimeout as delay } from "node:timers/promises";
6998
7005
 
6999
7006
  // src/spawn.ts
@@ -7003,13 +7010,241 @@ import {
7003
7010
  spawn,
7004
7011
  spawnSync
7005
7012
  } from "node:child_process";
7013
+
7014
+ // src/uid-cache.ts
7015
+ import {
7016
+ existsSync,
7017
+ readdirSync,
7018
+ readFileSync,
7019
+ renameSync,
7020
+ rmSync,
7021
+ writeFileSync
7022
+ } from "node:fs";
7023
+ import { isAbsolute, join, resolve } from "node:path";
7024
+ import { fileURLToPath } from "node:url";
7025
+ var CACHE = join(".godot", "uid_cache.bin");
7026
+ var RES = "res://";
7027
+ var UID = "uid://";
7028
+ var NOTHING = { moved: [], dropped: [] };
7029
+ function uidCacheEntries(data) {
7030
+ if (data.length < 4) {
7031
+ return null;
7032
+ }
7033
+ const count = data.readUInt32LE(0);
7034
+ const paths = new Map;
7035
+ let at = 4;
7036
+ for (let entry = 0;entry < count; entry++) {
7037
+ if (at + 12 > data.length) {
7038
+ return null;
7039
+ }
7040
+ const id = data.readBigUInt64LE(at);
7041
+ const length = data.readUInt32LE(at + 8);
7042
+ at += 12;
7043
+ if (at + length > data.length) {
7044
+ return null;
7045
+ }
7046
+ paths.set(id, data.toString("utf8", at, at + length));
7047
+ at += length;
7048
+ }
7049
+ return [...paths];
7050
+ }
7051
+ function encodedUidCache(entries) {
7052
+ const parts = [Buffer.alloc(4)];
7053
+ parts[0]?.writeUInt32LE(entries.length, 0);
7054
+ for (const [id, path] of entries) {
7055
+ const named = Buffer.from(path, "utf8");
7056
+ const head = Buffer.alloc(12);
7057
+ head.writeBigUInt64LE(id, 0);
7058
+ head.writeUInt32LE(named.length, 8);
7059
+ parts.push(head, named);
7060
+ }
7061
+ return Buffer.concat(parts);
7062
+ }
7063
+ function uidOfText(text) {
7064
+ if (!text.startsWith(UID) || text.length === UID.length) {
7065
+ return null;
7066
+ }
7067
+ let id = 0n;
7068
+ for (const character of text.slice(UID.length)) {
7069
+ const code = character.charCodeAt(0);
7070
+ let digit;
7071
+ if (code >= 97 && code <= 122) {
7072
+ digit = code - 97;
7073
+ } else if (code >= 48 && code <= 57) {
7074
+ digit = code - 48 + 25;
7075
+ } else {
7076
+ return null;
7077
+ }
7078
+ id = id * 34n + BigInt(digit);
7079
+ }
7080
+ return id & 0x7fffffffffffffffn;
7081
+ }
7082
+ function presenceIn(projectPath) {
7083
+ const listed = new Map;
7084
+ return (path) => {
7085
+ const inside = path.slice(RES.length);
7086
+ const slash = inside.lastIndexOf("/");
7087
+ const folder = slash < 0 ? "" : inside.slice(0, slash);
7088
+ let names = listed.get(folder);
7089
+ if (names === undefined) {
7090
+ try {
7091
+ names = new Set(readdirSync(join(projectPath, folder)));
7092
+ } catch {
7093
+ names = new Set;
7094
+ }
7095
+ listed.set(folder, names);
7096
+ }
7097
+ return names.has(inside.slice(slash + 1));
7098
+ };
7099
+ }
7100
+ var IMPORT_UID = /^uid="(uid:\/\/[^"]*)"/m;
7101
+ var HEADER_UID = /^\[gd_(?:scene|resource)\b[^\]\n]*\buid="(uid:\/\/[^"]*)"/;
7102
+ function declared(path, name) {
7103
+ const read = () => {
7104
+ try {
7105
+ return readFileSync(path, "utf8");
7106
+ } catch {
7107
+ return null;
7108
+ }
7109
+ };
7110
+ if (name.endsWith(".uid")) {
7111
+ const text = read()?.trim();
7112
+ return text === undefined ? null : [text, name.slice(0, -".uid".length)];
7113
+ }
7114
+ const pattern = name.endsWith(".import") ? IMPORT_UID : name.endsWith(".tscn") || name.endsWith(".tres") ? HEADER_UID : null;
7115
+ const text = pattern === null ? undefined : read()?.match(pattern)?.[1];
7116
+ if (text === undefined) {
7117
+ return null;
7118
+ }
7119
+ return [text, name.endsWith(".import") ? name.slice(0, -".import".length) : name];
7120
+ }
7121
+ function declaringFiles(projectPath, wanted) {
7122
+ const found = new Map;
7123
+ const visit = (directory, prefix) => {
7124
+ let entries;
7125
+ try {
7126
+ entries = readdirSync(directory, { withFileTypes: true });
7127
+ } catch {
7128
+ return;
7129
+ }
7130
+ if (entries.some((entry) => entry.name === ".gdignore")) {
7131
+ return;
7132
+ }
7133
+ for (const entry of entries) {
7134
+ if (found.size === wanted.size) {
7135
+ return;
7136
+ }
7137
+ if (entry.name.startsWith(".")) {
7138
+ continue;
7139
+ }
7140
+ const path = join(directory, entry.name);
7141
+ const spelled = `${prefix}${entry.name}`;
7142
+ if (entry.isDirectory()) {
7143
+ visit(path, `${spelled}/`);
7144
+ continue;
7145
+ }
7146
+ const declaration = entry.isFile() ? declared(path, spelled) : null;
7147
+ const id = declaration === null ? null : uidOfText(declaration[0]);
7148
+ if (declaration !== null && id !== null && wanted.has(id) && !found.has(id)) {
7149
+ found.set(id, `${RES}${declaration[1]}`);
7150
+ }
7151
+ }
7152
+ };
7153
+ visit(projectPath, "");
7154
+ return found;
7155
+ }
7156
+ function mendUidCache(projectPath) {
7157
+ const cache = join(projectPath, CACHE);
7158
+ let data;
7159
+ try {
7160
+ data = readFileSync(cache);
7161
+ } catch {
7162
+ return NOTHING;
7163
+ }
7164
+ const entries = uidCacheEntries(data);
7165
+ if (entries === null) {
7166
+ return NOTHING;
7167
+ }
7168
+ const present = presenceIn(projectPath);
7169
+ const gone = entries.filter(([, path]) => path.startsWith(RES) && !present(path));
7170
+ if (gone.length === 0) {
7171
+ return NOTHING;
7172
+ }
7173
+ const goneIds = new Set(gone.map(([id]) => id));
7174
+ const now = declaringFiles(projectPath, goneIds);
7175
+ const moved = [];
7176
+ const dropped = [];
7177
+ for (const [id, path] of gone) {
7178
+ const to = now.get(id);
7179
+ if (to === undefined) {
7180
+ dropped.push([id, path]);
7181
+ } else {
7182
+ moved.push([id, path, to]);
7183
+ }
7184
+ }
7185
+ const written = `${cache}.${String(process.pid)}.mending`;
7186
+ const kept = entries.flatMap(([id, path]) => {
7187
+ if (!goneIds.has(id)) {
7188
+ return [[id, path]];
7189
+ }
7190
+ const to = now.get(id);
7191
+ return to === undefined ? [] : [[id, to]];
7192
+ });
7193
+ writeFileSync(written, encodedUidCache(kept));
7194
+ try {
7195
+ renameSync(written, cache);
7196
+ } catch (error) {
7197
+ rmSync(written, { force: true });
7198
+ throw error;
7199
+ }
7200
+ return { moved, dropped };
7201
+ }
7202
+ function projectStarted(args, cwd) {
7203
+ const at = args.indexOf("--path");
7204
+ const named = at < 0 ? undefined : args[at + 1];
7205
+ if (named === undefined) {
7206
+ return null;
7207
+ }
7208
+ const from = cwd === undefined ? process.cwd() : cwd instanceof URL ? fileURLToPath(cwd) : cwd;
7209
+ const project = isAbsolute(named) ? named : resolve(from, named);
7210
+ return existsSync(join(project, "project.godot")) ? project : null;
7211
+ }
7212
+ function mendBeforeStart(args, cwd) {
7213
+ const project = projectStarted(args, cwd);
7214
+ if (project === null) {
7215
+ return;
7216
+ }
7217
+ const where = join(project, CACHE);
7218
+ try {
7219
+ const { moved, dropped } = mendUidCache(project);
7220
+ if (moved.length > 0) {
7221
+ const named = moved.map(([, from, to]) => `${from} to ${to}`).join(", ");
7222
+ process.stderr.write(`uid cache: moved what names a file gone in ${where}: ${named}
7223
+ `);
7224
+ }
7225
+ if (dropped.length > 0) {
7226
+ const named = dropped.map(([, path]) => path).join(", ");
7227
+ process.stderr.write(`uid cache: dropped what names a file gone from ${where}: ${named}
7228
+ `);
7229
+ }
7230
+ } catch (error) {
7231
+ const said = error instanceof Error ? error.message : String(error);
7232
+ process.stderr.write(`uid cache: could not rewrite ${where}, which names files gone: ${said}
7233
+ `);
7234
+ }
7235
+ }
7236
+
7237
+ // src/spawn.ts
7006
7238
  function spawnHidden(command, args, options = {}) {
7239
+ mendBeforeStart(args, options.cwd);
7007
7240
  return spawn(command, [...args], { ...options, windowsHide: true });
7008
7241
  }
7009
7242
  function spawnShown(command, args, options = {}) {
7243
+ mendBeforeStart(args, options.cwd);
7010
7244
  return spawn(command, [...args], { ...options, windowsHide: false });
7011
7245
  }
7012
7246
  function runHidden(command, args, options = {}) {
7247
+ mendBeforeStart(args, options.cwd ?? undefined);
7013
7248
  return new Promise((resolve, reject) => {
7014
7249
  execFile(command, [...args], { ...options, encoding: "utf8", windowsHide: true }, (error, stdout, stderr) => {
7015
7250
  if (error !== null) {
@@ -7037,14 +7272,14 @@ class CompiledHelper {
7037
7272
  this.digest = createHash("sha256").update(source).digest("hex").slice(0, 16);
7038
7273
  }
7039
7274
  assembly(directory = tmpdir()) {
7040
- return join(directory, `${this.file}-${this.digest}.dll`);
7275
+ return join2(directory, `${this.file}-${this.digest}.dll`);
7041
7276
  }
7042
7277
  async ensure(directory = tmpdir(), budgetMs = HELPER_COMPILE_MS, wait = true) {
7043
7278
  const assembly = this.assembly(directory);
7044
7279
  const lock = `${assembly}.compiling`;
7045
7280
  const until = Date.now() + budgetMs;
7046
7281
  while (Date.now() < until) {
7047
- if (existsSync(assembly)) {
7282
+ if (existsSync2(assembly)) {
7048
7283
  return null;
7049
7284
  }
7050
7285
  let mine = false;
@@ -7058,14 +7293,14 @@ class CompiledHelper {
7058
7293
  try {
7059
7294
  return await this.compile(directory, assembly, until - Date.now());
7060
7295
  } finally {
7061
- rmSync(lock, { force: true });
7296
+ rmSync2(lock, { force: true });
7062
7297
  }
7063
7298
  }
7064
7299
  try {
7065
- const holder = Number(readFileSync(lock, "utf8").trim());
7300
+ const holder = Number(readFileSync2(lock, "utf8").trim());
7066
7301
  const old = Date.now() - statSync(lock).mtimeMs > budgetMs;
7067
7302
  if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
7068
- rmSync(lock, { force: true });
7303
+ rmSync2(lock, { force: true });
7069
7304
  continue;
7070
7305
  }
7071
7306
  } catch {}
@@ -7077,9 +7312,9 @@ class CompiledHelper {
7077
7312
  return `the ${this.named} was not compiled within ${String(budgetMs)} ms`;
7078
7313
  }
7079
7314
  async compile(directory, assembly, budgetMs) {
7080
- const scratch = mkdtempSync(join(directory, `${this.file}-source-`));
7081
- const source = join(scratch, "helper.cs");
7082
- writeFileSync(source, this.source, "utf8");
7315
+ const scratch = mkdtempSync(join2(directory, `${this.file}-source-`));
7316
+ const source = join2(scratch, "helper.cs");
7317
+ writeFileSync2(source, this.source, "utf8");
7083
7318
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
7084
7319
  const script = [
7085
7320
  "$ErrorActionPreference = 'Stop'",
@@ -7116,11 +7351,11 @@ class CompiledHelper {
7116
7351
  });
7117
7352
  compiler.once("close", (code) => {
7118
7353
  clearTimeout(timer);
7119
- resolve(existsSync(assembly) ? null : `Windows PowerShell could not compile the ${this.named}${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
7354
+ resolve(existsSync2(assembly) ? null : `Windows PowerShell could not compile the ${this.named}${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
7120
7355
  });
7121
7356
  });
7122
7357
  } finally {
7123
- rmSync(scratch, { recursive: true, force: true });
7358
+ rmSync2(scratch, { recursive: true, force: true });
7124
7359
  }
7125
7360
  }
7126
7361
  }
@@ -7379,7 +7614,7 @@ function windowsCommandLine(command, args) {
7379
7614
  }
7380
7615
  function helperFailure(file) {
7381
7616
  try {
7382
- const said = readFileSync2(file, "utf8").trim();
7617
+ const said = readFileSync3(file, "utf8").trim();
7383
7618
  return said === "" ? null : said;
7384
7619
  } catch {
7385
7620
  return null;
@@ -7388,11 +7623,12 @@ function helperFailure(file) {
7388
7623
  function helperStep(stepFile) {
7389
7624
  let step = null;
7390
7625
  try {
7391
- step = readFileSync2(stepFile, "utf8").trim();
7626
+ step = readFileSync3(stepFile, "utf8").trim();
7392
7627
  } catch {}
7393
7628
  return step === null || step === "" ? "Windows PowerShell had not begun running the helper" : `the helper was ${step}`;
7394
7629
  }
7395
7630
  function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7631
+ mendBeforeStart(args, how.directory);
7396
7632
  const fields = [
7397
7633
  how.desktop ?? "",
7398
7634
  windowsCommandLine(command, args),
@@ -7403,7 +7639,7 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7403
7639
  files.step,
7404
7640
  how.affinity === undefined ? "" : how.affinity.toString()
7405
7641
  ];
7406
- writeFileSync2(files.spec, fields.map((field) => Buffer.from(field, "utf8").toString("base64")).join(`
7642
+ writeFileSync3(files.spec, fields.map((field) => Buffer.from(field, "utf8").toString("base64")).join(`
7407
7643
  `), "utf8");
7408
7644
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
7409
7645
  const script = [
@@ -7435,12 +7671,12 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7435
7671
  // src/engine-slots.ts
7436
7672
  import {
7437
7673
  closeSync as closeSync2,
7438
- existsSync as existsSync2,
7674
+ existsSync as existsSync3,
7439
7675
  mkdirSync,
7440
7676
  openSync as openSync2,
7441
- readdirSync,
7442
- readFileSync as readFileSync5,
7443
- rmSync as rmSync3,
7677
+ readdirSync as readdirSync2,
7678
+ readFileSync as readFileSync6,
7679
+ rmSync as rmSync4,
7444
7680
  statSync as statSync3,
7445
7681
  watch,
7446
7682
  writeSync as writeSync2
@@ -7486,18 +7722,19 @@ var buffered = "";
7486
7722
  var next = 1;
7487
7723
  var out = null;
7488
7724
  var queued = [];
7725
+ function holdOn(pipe, wanted) {
7726
+ const call = Reflect.get(pipe, wanted ? "ref" : "unref");
7727
+ if (typeof call === "function") {
7728
+ Reflect.apply(call, pipe, []);
7729
+ }
7730
+ }
7489
7731
  var held = false;
7490
7732
  function holding(child, wanted) {
7491
7733
  if (held === wanted) {
7492
7734
  return;
7493
7735
  }
7494
7736
  held = wanted;
7495
- const stdout = child.stdout;
7496
- if (wanted) {
7497
- stdout.ref?.();
7498
- } else {
7499
- stdout.unref?.();
7500
- }
7737
+ holdOn(child.stdout, wanted);
7501
7738
  }
7502
7739
  function running() {
7503
7740
  if (helper?.exitCode === null && helper.signalCode === null) {
@@ -7539,8 +7776,8 @@ function running() {
7539
7776
  child.once("exit", gone);
7540
7777
  child.once("error", gone);
7541
7778
  child.unref();
7542
- child.stdin.unref?.();
7543
- child.stderr.unref?.();
7779
+ holdOn(child.stdin, false);
7780
+ holdOn(child.stderr, false);
7544
7781
  held = true;
7545
7782
  holding(child, false);
7546
7783
  helper = child;
@@ -7969,14 +8206,14 @@ async function loadingTheTable() {
7969
8206
  return `$null = [Reflection.Assembly]::LoadFrom('${TABLE.assembly().replaceAll("'", "''")}')`;
7970
8207
  }
7971
8208
  async function askProcessTable(call, withinMs) {
7972
- return askWindows(`${await loadingTheTable()}; [GdhProcessTable]::${call}`, withinMs);
8209
+ return await askWindows(`${await loadingTheTable()}; [GdhProcessTable]::${call}`, withinMs);
7973
8210
  }
7974
8211
 
7975
8212
  // src/engine-spent.ts
7976
8213
  var watched = new Set;
7977
8214
 
7978
8215
  // src/harnesses.ts
7979
- import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2 } from "node:path";
8216
+ import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join4, relative as relative2, resolve as resolve3 } from "node:path";
7980
8217
 
7981
8218
  // node_modules/yaml/dist/index.js
7982
8219
  var composer = require_composer();
@@ -8026,13 +8263,13 @@ var $visitAsync = visit.visitAsync;
8026
8263
 
8027
8264
  // src/paths.ts
8028
8265
  import { realpathSync, statSync as statSync2 } from "node:fs";
8029
- import { basename, dirname, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
8266
+ import { basename, dirname, isAbsolute as isAbsolute2, join as join3, relative, resolve as resolve2, sep } from "node:path";
8030
8267
  function realAsFarAsItExists(path) {
8031
8268
  const missing = [];
8032
8269
  let at = path;
8033
8270
  for (;; ) {
8034
8271
  try {
8035
- return join2(realpathSync.native(at), ...missing);
8272
+ return join3(realpathSync.native(at), ...missing);
8036
8273
  } catch {
8037
8274
  const parent = dirname(at);
8038
8275
  if (parent === at) {
@@ -8044,6 +8281,14 @@ function realAsFarAsItExists(path) {
8044
8281
  }
8045
8282
  }
8046
8283
 
8284
+ // src/records.ts
8285
+ function isRecord(value) {
8286
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8287
+ }
8288
+ function isStringList(value) {
8289
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
8290
+ }
8291
+
8047
8292
  // src/harnesses.ts
8048
8293
  var SERVER_KEY = "gdharness";
8049
8294
  function tomlArray(values) {
@@ -8066,7 +8311,7 @@ var HARNESSES = [
8066
8311
  shape: "plain",
8067
8312
  home: ".claude",
8068
8313
  marker: ".claude",
8069
- skills: { dir: join3(".claude", "skills"), shared: false }
8314
+ skills: { dir: join4(".claude", "skills"), shared: false }
8070
8315
  },
8071
8316
  {
8072
8317
  id: "copilot-cli",
@@ -8076,7 +8321,7 @@ var HARNESSES = [
8076
8321
  container: "mcpServers",
8077
8322
  shape: "plain",
8078
8323
  home: ".copilot",
8079
- skills: { dir: join3(".github", "skills"), shared: true },
8324
+ skills: { dir: join4(".github", "skills"), shared: true },
8080
8325
  manual: "Trust the folder when Copilot CLI first asks, or it reads no config here."
8081
8326
  },
8082
8327
  {
@@ -8102,22 +8347,22 @@ var HARNESSES = [
8102
8347
  id: "cursor",
8103
8348
  name: "Cursor",
8104
8349
  scope: "project",
8105
- file: join3(".cursor", "mcp.json"),
8350
+ file: join4(".cursor", "mcp.json"),
8106
8351
  container: "mcpServers",
8107
8352
  shape: "plain",
8108
8353
  home: ".cursor",
8109
- skills: { dir: join3(".cursor", "skills"), shared: true },
8354
+ skills: { dir: join4(".cursor", "skills"), shared: true },
8110
8355
  manual: "Cursor asks before each tool call by default, and the server can be toggled off."
8111
8356
  },
8112
8357
  {
8113
8358
  id: "vscode",
8114
8359
  name: "VS Code",
8115
8360
  scope: "project",
8116
- file: join3(".vscode", "mcp.json"),
8361
+ file: join4(".vscode", "mcp.json"),
8117
8362
  container: "servers",
8118
8363
  shape: "typed",
8119
8364
  home: ".vscode",
8120
- skills: { dir: join3(".github", "skills"), shared: true },
8365
+ skills: { dir: join4(".github", "skills"), shared: true },
8121
8366
  manual: "VS Code asks you to trust a server the first time it starts one."
8122
8367
  },
8123
8368
  {
@@ -8127,45 +8372,45 @@ var HARNESSES = [
8127
8372
  file: "opencode.json",
8128
8373
  container: "mcp",
8129
8374
  shape: "opencode",
8130
- home: join3(".config", "opencode"),
8375
+ home: join4(".config", "opencode"),
8131
8376
  marker: ".opencode",
8132
- skills: { dir: join3(".opencode", "skills"), shared: true }
8377
+ skills: { dir: join4(".opencode", "skills"), shared: true }
8133
8378
  },
8134
8379
  {
8135
8380
  id: "junie",
8136
8381
  name: "Junie",
8137
8382
  scope: "project",
8138
- file: join3(".junie", "mcp", "mcp.json"),
8383
+ file: join4(".junie", "mcp", "mcp.json"),
8139
8384
  container: "mcpServers",
8140
8385
  shape: "plain",
8141
8386
  home: ".junie",
8142
- skills: { dir: join3(".junie", "skills"), shared: true }
8387
+ skills: { dir: join4(".junie", "skills"), shared: true }
8143
8388
  },
8144
8389
  {
8145
8390
  id: "kiro",
8146
8391
  name: "Kiro",
8147
8392
  scope: "project",
8148
- file: join3(".kiro", "settings", "mcp.json"),
8393
+ file: join4(".kiro", "settings", "mcp.json"),
8149
8394
  container: "mcpServers",
8150
8395
  shape: "plain",
8151
8396
  home: ".kiro",
8152
- skills: { dir: join3(".kiro", "skills"), shared: false }
8397
+ skills: { dir: join4(".kiro", "skills"), shared: false }
8153
8398
  },
8154
8399
  {
8155
8400
  id: "gemini",
8156
8401
  name: "Gemini CLI",
8157
8402
  scope: "project",
8158
- file: join3(".gemini", "settings.json"),
8403
+ file: join4(".gemini", "settings.json"),
8159
8404
  container: "mcpServers",
8160
8405
  shape: "plain",
8161
8406
  home: ".gemini",
8162
- skills: { dir: join3(".gemini", "skills"), shared: true }
8407
+ skills: { dir: join4(".gemini", "skills"), shared: true }
8163
8408
  },
8164
8409
  {
8165
8410
  id: "roo",
8166
8411
  name: "Roo Code",
8167
8412
  scope: "project",
8168
- file: join3(".roo", "mcp.json"),
8413
+ file: join4(".roo", "mcp.json"),
8169
8414
  container: "mcpServers",
8170
8415
  shape: "plain"
8171
8416
  },
@@ -8173,7 +8418,7 @@ var HARNESSES = [
8173
8418
  id: "kilo",
8174
8419
  name: "Kilo Code",
8175
8420
  scope: "project",
8176
- file: join3(".kilocode", "mcp.json"),
8421
+ file: join4(".kilocode", "mcp.json"),
8177
8422
  container: "mcpServers",
8178
8423
  shape: "plain"
8179
8424
  },
@@ -8181,16 +8426,16 @@ var HARNESSES = [
8181
8426
  id: "amazon-q",
8182
8427
  name: "Amazon Q Developer CLI",
8183
8428
  scope: "project",
8184
- file: join3(".amazonq", "mcp.json"),
8429
+ file: join4(".amazonq", "mcp.json"),
8185
8430
  container: "mcpServers",
8186
8431
  shape: "plain",
8187
- home: join3(".aws", "amazonq")
8432
+ home: join4(".aws", "amazonq")
8188
8433
  },
8189
8434
  {
8190
8435
  id: "zed",
8191
8436
  name: "Zed",
8192
8437
  scope: "project",
8193
- file: join3(".zed", "settings.json"),
8438
+ file: join4(".zed", "settings.json"),
8194
8439
  container: "context_servers",
8195
8440
  shape: "plain"
8196
8441
  },
@@ -8198,16 +8443,16 @@ var HARNESSES = [
8198
8443
  id: "amp",
8199
8444
  name: "Amp",
8200
8445
  scope: "project",
8201
- file: join3(".amp", "settings.json"),
8446
+ file: join4(".amp", "settings.json"),
8202
8447
  container: "amp.mcpServers",
8203
8448
  shape: "plain",
8204
- home: join3(".config", "amp")
8449
+ home: join4(".config", "amp")
8205
8450
  },
8206
8451
  {
8207
8452
  id: "warp",
8208
8453
  name: "Warp",
8209
8454
  scope: "project",
8210
- file: join3(".warp", ".mcp.json"),
8455
+ file: join4(".warp", ".mcp.json"),
8211
8456
  container: "mcpServers",
8212
8457
  shape: "plain",
8213
8458
  home: ".warp",
@@ -8217,7 +8462,7 @@ var HARNESSES = [
8217
8462
  id: "trae",
8218
8463
  name: "Trae",
8219
8464
  scope: "project",
8220
- file: join3(".trae", "mcp.json"),
8465
+ file: join4(".trae", "mcp.json"),
8221
8466
  container: "mcpServers",
8222
8467
  shape: "plain",
8223
8468
  home: ".trae"
@@ -8226,7 +8471,7 @@ var HARNESSES = [
8226
8471
  id: "factory",
8227
8472
  name: "Factory Droid",
8228
8473
  scope: "project",
8229
- file: join3(".factory", "mcp.json"),
8474
+ file: join4(".factory", "mcp.json"),
8230
8475
  container: "mcpServers",
8231
8476
  shape: "typed",
8232
8477
  home: ".factory"
@@ -8235,7 +8480,7 @@ var HARNESSES = [
8235
8480
  id: "tabnine",
8236
8481
  name: "Tabnine",
8237
8482
  scope: "project",
8238
- file: join3(".tabnine", "mcp_servers.json"),
8483
+ file: join4(".tabnine", "mcp_servers.json"),
8239
8484
  container: "mcpServers",
8240
8485
  shape: "plain",
8241
8486
  home: ".tabnine"
@@ -8252,7 +8497,7 @@ var HARNESSES = [
8252
8497
  id: "pi",
8253
8498
  name: "pi",
8254
8499
  scope: "project",
8255
- file: join3(".pi", "mcp.json"),
8500
+ file: join4(".pi", "mcp.json"),
8256
8501
  container: "mcpServers",
8257
8502
  shape: "plain",
8258
8503
  home: ".pi"
@@ -8261,7 +8506,7 @@ var HARNESSES = [
8261
8506
  id: "mistral-vibe",
8262
8507
  name: "Mistral Vibe",
8263
8508
  scope: "project",
8264
- file: join3(".vibe", "config.toml"),
8509
+ file: join4(".vibe", "config.toml"),
8265
8510
  container: "mcp_servers",
8266
8511
  shape: "plain",
8267
8512
  toml: {
@@ -8317,7 +8562,7 @@ var HARNESSES = [
8317
8562
  id: "codex",
8318
8563
  name: "Codex CLI",
8319
8564
  scope: "home",
8320
- file: join3(".codex", "config.toml"),
8565
+ file: join4(".codex", "config.toml"),
8321
8566
  container: "mcp_servers",
8322
8567
  shape: "plain",
8323
8568
  manual: "Codex reads its config at startup, so restart it.",
@@ -8336,20 +8581,20 @@ var HARNESSES = [
8336
8581
  id: "cline",
8337
8582
  name: "Cline",
8338
8583
  scope: "home",
8339
- file: join3(".cline", "data", "settings", "cline_mcp_settings.json"),
8584
+ file: join4(".cline", "data", "settings", "cline_mcp_settings.json"),
8340
8585
  container: "mcpServers",
8341
8586
  shape: "plain",
8342
8587
  marker: ".cline",
8343
- skills: { dir: join3(".cline", "skills"), shared: false }
8588
+ skills: { dir: join4(".cline", "skills"), shared: false }
8344
8589
  },
8345
8590
  {
8346
8591
  id: "goose",
8347
8592
  name: "Goose",
8348
8593
  scope: "home",
8349
8594
  file: {
8350
- win32: { appData: join3("Block", "goose", "config", "config.yaml") },
8351
- darwin: join3(".config", "goose", "config.yaml"),
8352
- linux: join3(".config", "goose", "config.yaml")
8595
+ win32: { appData: join4("Block", "goose", "config", "config.yaml") },
8596
+ darwin: join4(".config", "goose", "config.yaml"),
8597
+ linux: join4(".config", "goose", "config.yaml")
8353
8598
  },
8354
8599
  container: "extensions",
8355
8600
  shape: "plain",
@@ -8370,19 +8615,19 @@ var HARNESSES = [
8370
8615
  id: "windsurf",
8371
8616
  name: "Windsurf",
8372
8617
  scope: "home",
8373
- file: join3(".codeium", "windsurf", "mcp_config.json"),
8618
+ file: join4(".codeium", "windsurf", "mcp_config.json"),
8374
8619
  container: "mcpServers",
8375
8620
  shape: "plain",
8376
- skills: { dir: join3(".windsurf", "skills"), shared: true }
8621
+ skills: { dir: join4(".windsurf", "skills"), shared: true }
8377
8622
  },
8378
8623
  {
8379
8624
  id: "hermes",
8380
8625
  name: "Hermes",
8381
8626
  scope: "home",
8382
- file: join3(".hermes", "config.yaml"),
8627
+ file: join4(".hermes", "config.yaml"),
8383
8628
  container: "mcp_servers",
8384
8629
  shape: "plain",
8385
- skills: { dir: join3(".hermes", "skills"), shared: true },
8630
+ skills: { dir: join4(".hermes", "skills"), shared: true },
8386
8631
  yaml: {
8387
8632
  path: ["mcp_servers", SERVER_KEY],
8388
8633
  entry: (launch) => ({
@@ -8397,7 +8642,7 @@ var HARNESSES = [
8397
8642
  id: "openclaw",
8398
8643
  name: "OpenClaw",
8399
8644
  scope: "home",
8400
- file: join3(".openclaw", "openclaw.json"),
8645
+ file: join4(".openclaw", "openclaw.json"),
8401
8646
  container: "mcpServers",
8402
8647
  shape: "plain"
8403
8648
  },
@@ -8406,9 +8651,9 @@ var HARNESSES = [
8406
8651
  name: "Claude Desktop",
8407
8652
  scope: "home",
8408
8653
  file: {
8409
- win32: { appData: join3("Claude", "claude_desktop_config.json") },
8410
- darwin: join3("Library", "Application Support", "Claude", "claude_desktop_config.json"),
8411
- linux: join3(".config", "Claude", "claude_desktop_config.json")
8654
+ win32: { appData: join4("Claude", "claude_desktop_config.json") },
8655
+ darwin: join4("Library", "Application Support", "Claude", "claude_desktop_config.json"),
8656
+ linux: join4(".config", "Claude", "claude_desktop_config.json")
8412
8657
  },
8413
8658
  container: "mcpServers",
8414
8659
  shape: "plain"
@@ -8417,7 +8662,7 @@ var HARNESSES = [
8417
8662
  id: "zeroclaw",
8418
8663
  name: "ZeroClaw",
8419
8664
  scope: "home",
8420
- file: join3(".zeroclaw", "config.toml"),
8665
+ file: join4(".zeroclaw", "config.toml"),
8421
8666
  container: "mcp.servers",
8422
8667
  shape: "plain",
8423
8668
  toml: {
@@ -8438,7 +8683,7 @@ var HARNESSES = [
8438
8683
  id: "deepcode",
8439
8684
  name: "Deep Code",
8440
8685
  scope: "home",
8441
- file: join3(".deepcode", "settings.json"),
8686
+ file: join4(".deepcode", "settings.json"),
8442
8687
  container: "mcpServers",
8443
8688
  shape: "plain"
8444
8689
  },
@@ -8446,7 +8691,7 @@ var HARNESSES = [
8446
8691
  id: "nanobot",
8447
8692
  name: "nanobot",
8448
8693
  scope: "home",
8449
- file: join3(".nanobot", "config.json"),
8694
+ file: join4(".nanobot", "config.json"),
8450
8695
  container: "mcpServers",
8451
8696
  shape: "plain",
8452
8697
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
@@ -8455,7 +8700,7 @@ var HARNESSES = [
8455
8700
  id: "autohand",
8456
8701
  name: "Autohand",
8457
8702
  scope: "home",
8458
- file: join3(".autohand", "config.json"),
8703
+ file: join4(".autohand", "config.json"),
8459
8704
  container: "mcpServers",
8460
8705
  shape: "plain",
8461
8706
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
@@ -8464,7 +8709,7 @@ var HARNESSES = [
8464
8709
  var OURS = ["GODOT_PATH", "GDHARNESS_PROJECT"];
8465
8710
  function environmentFor(launch, held) {
8466
8711
  const kept = {};
8467
- if (typeof held === "object" && held !== null && !Array.isArray(held)) {
8712
+ if (isRecord(held)) {
8468
8713
  for (const [name, value] of Object.entries(held)) {
8469
8714
  if (typeof value === "string" && !OURS.includes(name)) {
8470
8715
  kept[name] = value;
@@ -8474,7 +8719,7 @@ function environmentFor(launch, held) {
8474
8719
  return { ...kept, ...launch.env };
8475
8720
  }
8476
8721
  function entryFor(shape, launch, held) {
8477
- const before = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
8722
+ const before = isRecord(held) ? { ...held } : {};
8478
8723
  if (shape === "opencode") {
8479
8724
  return {
8480
8725
  ...before,
@@ -8500,7 +8745,15 @@ var LOG_LIMIT_BYTES = 128 * 1024 * 1024;
8500
8745
  var DECLARING = new Set(["var", "const", "func", "signal", "enum", "class", "for", "class_name"]);
8501
8746
  var NAME = "[\\p{L}_][\\p{L}\\p{N}_]*";
8502
8747
  var ANNOTATIONS = "(?:@[\\p{L}_][\\p{L}\\p{N}_]*(?:\\([^)]*\\))?\\s+)*";
8503
- var DECLARATION_LINE = new RegExp(`^(${ANNOTATIONS})(static\\s+)?(func|var|const|signal|enum|class)\\s+(${NAME})`, "u");
8748
+ var DECLARATION_KEYWORDS = [
8749
+ "func",
8750
+ "var",
8751
+ "const",
8752
+ "signal",
8753
+ "enum",
8754
+ "class"
8755
+ ];
8756
+ var DECLARATION_LINE = new RegExp(`^(${ANNOTATIONS})(static\\s+)?(${DECLARATION_KEYWORDS.join("|")})\\s+(${NAME})`, "u");
8504
8757
  var CLASS_NAME_LINE = new RegExp(`^${ANNOTATIONS}class_name\\s+(${NAME})`, "u");
8505
8758
  var EXTENDS_LINE = new RegExp(`^${ANNOTATIONS}(?:class_name\\s+${NAME}\\s+)?extends\\s`, "u");
8506
8759
  var FUNC_LINE = new RegExp(`^[ \\t]*${ANNOTATIONS}(?:static\\s+)?func\\s+(${NAME})\\s*\\(`, "u");
@@ -8554,7 +8807,7 @@ var KEYWORDS = new Set([
8554
8807
  ]);
8555
8808
 
8556
8809
  // src/whole-file.ts
8557
- import { readFileSync as readFileSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
8810
+ import { readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
8558
8811
  var HELD_FILE_MS = 1000;
8559
8812
  var HELD_FILE_RETRY_MS = 10;
8560
8813
  var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
@@ -8562,9 +8815,9 @@ function readWhole(path) {
8562
8815
  const until = Date.now() + HELD_FILE_MS;
8563
8816
  for (;; ) {
8564
8817
  try {
8565
- return readFileSync3(path, "utf8");
8818
+ return readFileSync4(path, "utf8");
8566
8819
  } catch (error) {
8567
- const code = error.code ?? "";
8820
+ const code = errorCode(error) ?? "";
8568
8821
  if (code === "ENOENT") {
8569
8822
  return null;
8570
8823
  }
@@ -8578,20 +8831,20 @@ function readWhole(path) {
8578
8831
  function writeWhole(path, text) {
8579
8832
  const partial = `${path}.${process.pid}.partial`;
8580
8833
  try {
8581
- writeFileSync3(partial, text, "utf8");
8834
+ writeFileSync4(partial, text, "utf8");
8582
8835
  } catch {
8583
8836
  return false;
8584
8837
  }
8585
8838
  const until = Date.now() + HELD_FILE_MS;
8586
8839
  for (;; ) {
8587
8840
  try {
8588
- renameSync(partial, path);
8841
+ renameSync2(partial, path);
8589
8842
  return true;
8590
8843
  } catch (error) {
8591
- const code = error.code ?? "";
8844
+ const code = errorCode(error) ?? "";
8592
8845
  if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
8593
8846
  try {
8594
- rmSync2(partial, { force: true });
8847
+ rmSync3(partial, { force: true });
8595
8848
  } catch {}
8596
8849
  return false;
8597
8850
  }
@@ -8603,10 +8856,10 @@ function removeWhole(path) {
8603
8856
  const until = Date.now() + HELD_FILE_MS;
8604
8857
  for (;; ) {
8605
8858
  try {
8606
- rmSync2(path, { force: true });
8859
+ rmSync3(path, { force: true });
8607
8860
  return true;
8608
8861
  } catch (error) {
8609
- const code = error.code ?? "";
8862
+ const code = errorCode(error) ?? "";
8610
8863
  if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
8611
8864
  return false;
8612
8865
  }
@@ -8616,7 +8869,7 @@ function removeWhole(path) {
8616
8869
  }
8617
8870
 
8618
8871
  // src/engine-leaks.ts
8619
- var REFCOUNTED_HELD = "A RefCounted alive at exit is held by a cycle of references (two holding each other, or one " + "holding a lambda that captures it) or by an Object never freed, rather than by a node left out " + "of the tree; a WeakRef on one side of the cycle, or clearing the reference once done, frees it.";
8872
+ var REFCOUNTED_HELD = "A RefCounted alive at exit is held by a cycle of references (two holding each other, or one " + "holding a lambda that captures it) or by an Object never freed, rather than by a node left out " + "of the tree. A WeakRef on one side of the cycle, or clearing the reference once done, frees it.";
8620
8873
  var WALK_STOPPED_SENTENCE = "The engine's shutdown stopped part way through its scripts (godot#117991, fixed in 4.8 by " + "godot#120976), so it reported as leaked at exit what it never got to free, and the report is the " + "engine's rather than the run's. gdharness holds every script loaded from a file past that walk, " + "which keeps it whole, so this one stopped on a script it could not hold, such as one made in code. " + "A leak of the run's own is not told apart in such a run.";
8621
8874
  var NAME2 = "[\\p{L}_][\\p{L}\\p{N}_]*";
8622
8875
  var EXPORTED = new RegExp(`@export(?!_(?:group|subgroup|category)\\b)\\w*(?:\\([^)]*\\))?\\s+(?:@${NAME2}(?:\\([^)]*\\))?\\s+)*` + `(?:static\\s+)?var\\s+(${NAME2})\\s*:\\s*(?:Array|Dictionary)\\s*\\[([^\\]]*)\\]`, "gu");
@@ -8669,7 +8922,7 @@ var MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
8669
8922
  var SCAN_BYTES = 1024 * 1024;
8670
8923
 
8671
8924
  // src/process-children.ts
8672
- import { readFileSync as readFileSync4 } from "node:fs";
8925
+ import { readFileSync as readFileSync5 } from "node:fs";
8673
8926
  import { basename as basename3 } from "node:path";
8674
8927
  async function startTimesOf(pids) {
8675
8928
  const began = new Map;
@@ -8711,7 +8964,7 @@ async function linuxStartTimes(pids) {
8711
8964
  const hertz = await ticksPerSecond();
8712
8965
  let booted;
8713
8966
  try {
8714
- booted = Number(/^btime\s+(\d+)$/m.exec(readFileSync4("/proc/stat", "utf8"))?.[1] ?? Number.NaN) * 1000;
8967
+ booted = Number(/^btime\s+(\d+)$/m.exec(readFileSync5("/proc/stat", "utf8"))?.[1] ?? Number.NaN) * 1000;
8715
8968
  } catch {
8716
8969
  return began;
8717
8970
  }
@@ -8721,7 +8974,7 @@ async function linuxStartTimes(pids) {
8721
8974
  for (const pid of pids) {
8722
8975
  let stat;
8723
8976
  try {
8724
- stat = readFileSync4(`/proc/${String(pid)}/stat`, "utf8");
8977
+ stat = readFileSync5(`/proc/${String(pid)}/stat`, "utf8");
8725
8978
  } catch {
8726
8979
  continue;
8727
8980
  }
@@ -8751,7 +9004,7 @@ async function ticksPerSecond() {
8751
9004
  }
8752
9005
  function printedBy(command, args, env) {
8753
9006
  return runHidden(command, args, { timeout: ASK_TIMEOUT_MS, env }).then(({ stdout }) => stdout, (error) => {
8754
- const printed = error.stdout;
9007
+ const printed = error instanceof Error && "stdout" in error ? error.stdout : undefined;
8755
9008
  return typeof printed === "string" ? printed : "";
8756
9009
  });
8757
9010
  }
@@ -8883,7 +9136,7 @@ function nameSlotEngine(lock, holder, engine) {
8883
9136
  }
8884
9137
  function giveBackSlot(lock, pid) {
8885
9138
  try {
8886
- if (holderOf(readFileSync5(lock, "utf8"))?.pid === pid) {
9139
+ if (holderOf(readFileSync6(lock, "utf8"))?.pid === pid) {
8887
9140
  removeWhole(lock);
8888
9141
  }
8889
9142
  } catch {}
@@ -8917,9 +9170,9 @@ function crashesReported() {
8917
9170
  }
8918
9171
 
8919
9172
  // src/game-debugger.ts
8920
- import { readFileSync as readFileSync7 } from "node:fs";
9173
+ import { readFileSync as readFileSync8 } from "node:fs";
8921
9174
  import { createServer } from "node:net";
8922
- import { join as join6 } from "node:path";
9175
+ import { join as join7 } from "node:path";
8923
9176
 
8924
9177
  // src/godot-variant.ts
8925
9178
  var NIL = 0;
@@ -9224,8 +9477,8 @@ function readMessage(body) {
9224
9477
  }
9225
9478
 
9226
9479
  // src/setup.ts
9227
- import { dirname as dirname3, join as join5 } from "node:path";
9228
- import { fileURLToPath } from "node:url";
9480
+ import { dirname as dirname3, join as join6 } from "node:path";
9481
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
9229
9482
 
9230
9483
  // src/dictionary.ts
9231
9484
  function emptyRecord() {
@@ -9339,25 +9592,25 @@ var IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
9339
9592
  var hashes = new Map;
9340
9593
 
9341
9594
  // src/scratch.ts
9342
- import { existsSync as existsSync3, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as rmSync4 } from "node:fs";
9595
+ import { existsSync as existsSync4, mkdtempSync as mkdtempSync2, readdirSync as readdirSync3, rmSync as rmSync5 } from "node:fs";
9343
9596
  import { tmpdir as tmpdir5 } from "node:os";
9344
- import { join as join4 } from "node:path";
9597
+ import { join as join5 } from "node:path";
9345
9598
  import process6 from "node:process";
9346
9599
  var SWEPT = { recursive: true, force: true };
9347
9600
  var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
9348
9601
  var waiting = new Set;
9349
9602
  var atExit = new Set;
9350
9603
  function scratchDirectory(kind) {
9351
- return mkdtempSync2(join4(tmpdir5(), `gdharness-${kind}-${process6.pid}-`));
9604
+ return mkdtempSync2(join5(tmpdir5(), `gdharness-${kind}-${process6.pid}-`));
9352
9605
  }
9353
- function discardWith(remove, paths) {
9606
+ function discardWith(removePath, paths) {
9354
9607
  const left = [];
9355
9608
  for (const path of paths) {
9356
9609
  if (!path) {
9357
9610
  continue;
9358
9611
  }
9359
9612
  try {
9360
- remove(path);
9613
+ removePath(path);
9361
9614
  } catch (error) {
9362
9615
  left.push({ path, reason: error instanceof Error ? error.message : String(error) });
9363
9616
  }
@@ -9372,7 +9625,7 @@ function discard(...paths) {
9372
9625
  return left;
9373
9626
  }
9374
9627
  function remove(path) {
9375
- rmSync4(path, SWEPT);
9628
+ rmSync5(path, SWEPT);
9376
9629
  }
9377
9630
  function tryAgain(path, attempt) {
9378
9631
  if (attempt === 0) {
@@ -9384,7 +9637,7 @@ function tryAgain(path, attempt) {
9384
9637
  lastTryOnExit();
9385
9638
  }
9386
9639
  const after = AGAIN_AFTER_MS[attempt];
9387
- if (after === undefined || !existsSync3(path)) {
9640
+ if (after === undefined || !existsSync4(path)) {
9388
9641
  waiting.delete(path);
9389
9642
  if (after === undefined) {
9390
9643
  atExit.add(path);
@@ -9533,7 +9786,7 @@ function parseIniLikeValue(value) {
9533
9786
  }
9534
9787
 
9535
9788
  // src/server-version.ts
9536
- import { readFileSync as readFileSync6 } from "node:fs";
9789
+ import { readFileSync as readFileSync7 } from "node:fs";
9537
9790
 
9538
9791
  // src/update-check.ts
9539
9792
  var CACHE_MS = 10 * 60 * 1000;
@@ -9544,8 +9797,9 @@ var MAX_RETRY_MS = 60 * 60 * 1000;
9544
9797
  var DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
9545
9798
  var SERVER_VERSION = (() => {
9546
9799
  try {
9547
- const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
9548
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
9800
+ const pkg = JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8"));
9801
+ const version = isRecord(pkg) ? pkg["version"] : undefined;
9802
+ return typeof version === "string" ? version : "0.0.0";
9549
9803
  } catch {
9550
9804
  return "0.0.0";
9551
9805
  }
@@ -9557,7 +9811,7 @@ var RUNTIME_AUTOLOAD = {
9557
9811
  path: "gdharness/loader.gd"
9558
9812
  };
9559
9813
  function shippedAddonsDirectory() {
9560
- return join5(dirname3(fileURLToPath(import.meta.url)), "godot", "addons");
9814
+ return join6(dirname3(fileURLToPath2(import.meta.url)), "godot", "addons");
9561
9815
  }
9562
9816
 
9563
9817
  // src/game-debugger.ts
@@ -9568,9 +9822,12 @@ var ADDON_LINES = 200;
9568
9822
  var RUNTIME_ADDON = "res://addons/gdharness_runtime/";
9569
9823
  var RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON}runtime_autoload.gd`;
9570
9824
  var frameEnd = null;
9825
+ function topIsTheAddon(frames) {
9826
+ return frames[0]?.script.startsWith(RUNTIME_ADDON) ?? false;
9827
+ }
9571
9828
  function frameEndLine() {
9572
9829
  if (frameEnd === null) {
9573
- const lines = readFileSync7(join6(shippedAddonsDirectory(), "gdharness_runtime", "runtime_autoload.gd"), "utf8").split(`
9830
+ const lines = readFileSync8(join7(shippedAddonsDirectory(), "gdharness_runtime", "runtime_autoload.gd"), "utf8").split(`
9574
9831
  `);
9575
9832
  const declared = lines.findIndex((line) => line.startsWith("func _notification("));
9576
9833
  if (declared === -1) {
@@ -9970,7 +10227,7 @@ class GameDebugger {
9970
10227
  if (entered === null) {
9971
10228
  return {
9972
10229
  paused: false,
9973
- note: `The game has been asked to stop and had not within ${PAUSE_MS / 1000}s. It takes the request between frames, and in a script every 2048 lines it runs, so its main thread is in a loop that sleeps between few lines, or inside one long engine call or a native extension's own loop. The request stays with the game, which stops when it next reaches that point; debug_state stack reads where once it has.`
10230
+ note: `The game has been asked to stop and had not within ${PAUSE_MS / 1000}s. It takes the request between frames, and in a script every 2048 lines it runs, so its main thread is in a loop that sleeps between few lines, or inside one long engine call or a native extension's own loop. The request stays with the game, which stops when it next reaches that point. debug_state stack reads where once it has.`
9974
10231
  };
9975
10232
  }
9976
10233
  }
@@ -9985,7 +10242,7 @@ class GameDebugger {
9985
10242
  if (again === null) {
9986
10243
  return {
9987
10244
  paused: false,
9988
- note: `The game stopped between frames, where no script runs, so it was let go to stop at the next line of script it runs, and none ran within ${STEP_MS / 1000}s. It stops there when one does; debug_state stack reads it then.`
10245
+ note: `The game stopped between frames, where no script runs, so it was let go to stop at the next line of script it runs, and none ran within ${STEP_MS / 1000}s. It stops there when one does, and debug_state stack reads it then.`
9989
10246
  };
9990
10247
  }
9991
10248
  }
@@ -9993,7 +10250,7 @@ class GameDebugger {
9993
10250
  if (past === "lost") {
9994
10251
  return {
9995
10252
  paused: false,
9996
- note: `The game stopped in gdharness's runtime addon and was let go on to reach its own script, and did not stop again within ${STEP_MS / 1000}s. It stops at the next line of script it runs; debug_state stack reads it then.`
10253
+ note: `The game stopped in gdharness's runtime addon and was let go on to reach its own script, and did not stop again within ${STEP_MS / 1000}s. It stops at the next line of script it runs, and debug_state stack reads it then.`
9997
10254
  };
9998
10255
  }
9999
10256
  this.stoppedBy("Paused");
@@ -10013,7 +10270,7 @@ class GameDebugger {
10013
10270
  let frames = await this.stack();
10014
10271
  let framesLeft = ADDON_FRAMES;
10015
10272
  let linesLeft = ADDON_LINES;
10016
- while (frames !== null && this.held !== null && (frames[0]?.script.startsWith(RUNTIME_ADDON) ?? false)) {
10273
+ while (frames !== null && this.held !== null && topIsTheAddon(frames)) {
10017
10274
  if (until !== undefined && Date.now() >= until) {
10018
10275
  return { frames, gaveUp: "time" };
10019
10276
  }
@@ -10093,7 +10350,7 @@ class GameDebugger {
10093
10350
  return {
10094
10351
  stepped: true,
10095
10352
  heldAgain: false,
10096
- note: `The step stopped in gdharness's runtime addon and was let go on to reach the game's own script, and did not stop again within ${STEP_MS / 1000}s. It stops at the next line of script the game runs; debug_state stack reads it then.`
10353
+ note: `The step stopped in gdharness's runtime addon and was let go on to reach the game's own script, and did not stop again within ${STEP_MS / 1000}s. It stops at the next line of script the game runs, and debug_state stack reads it then.`
10097
10354
  };
10098
10355
  }
10099
10356
  this.stoppedBy("Stepped");
@@ -10211,7 +10468,7 @@ class GameDebugger {
10211
10468
  return null;
10212
10469
  }
10213
10470
  return {
10214
- refused: frames.length === 0 ? "The game is held where no script runs, so it has no frames: debug_control step_over takes it to the next line of script." : `The stack has no frame ${frame}; its frames are ${frames.map((each) => each.id).join(", ")}.`
10471
+ refused: frames.length === 0 ? "The game is held where no script runs, so it has no frames: debug_control step_over takes it to the next line of script." : `The stack has no frame ${frame}. Its frames are ${frames.map((each) => each.id).join(", ")}.`
10215
10472
  };
10216
10473
  }
10217
10474
  close() {
@@ -10222,10 +10479,10 @@ class GameDebugger {
10222
10479
 
10223
10480
  // src/keeper-channel.ts
10224
10481
  import { randomUUID } from "node:crypto";
10225
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync5 } from "node:fs";
10482
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync6 } from "node:fs";
10226
10483
  import { connect, createServer as createServer2 } from "node:net";
10227
10484
  import { tmpdir as tmpdir6 } from "node:os";
10228
- import { join as join7 } from "node:path";
10485
+ import { join as join8 } from "node:path";
10229
10486
  import process7 from "node:process";
10230
10487
  var DEBUG_PREFIX = "debug ";
10231
10488
  function endedOrWhyNot(end) {
@@ -10241,11 +10498,41 @@ async function debuggedOrWhyNot(debug, asked) {
10241
10498
  return JSON.stringify({ refused: "This run has no debugger." });
10242
10499
  }
10243
10500
  try {
10244
- return JSON.stringify(await debug(JSON.parse(asked)));
10501
+ const request = debugRequestOf(JSON.parse(asked));
10502
+ if (request === null) {
10503
+ return JSON.stringify({ refused: `The debugger takes no request like ${asked.slice(0, 200)}.` });
10504
+ }
10505
+ return JSON.stringify(await debug(request));
10245
10506
  } catch (error) {
10246
10507
  return JSON.stringify({ refused: `The debugger could not answer: ${errorText(error)}` });
10247
10508
  }
10248
10509
  }
10510
+ var RESUMED_HOW = ["continue", "next", "step", "out"];
10511
+ function debugRequestOf(asked) {
10512
+ if (!isRecord(asked)) {
10513
+ return null;
10514
+ }
10515
+ const { op, script, line, set, how, frame, expression } = asked;
10516
+ switch (op) {
10517
+ case "breakpoint":
10518
+ return typeof script === "string" && typeof line === "number" && typeof set === "boolean" ? { op, script, line, set } : null;
10519
+ case "pause":
10520
+ case "stack":
10521
+ case "output":
10522
+ case "state":
10523
+ return { op };
10524
+ case "resume": {
10525
+ const resumed = RESUMED_HOW.find((one) => one === how);
10526
+ return resumed === undefined ? null : { op, how: resumed };
10527
+ }
10528
+ case "variables":
10529
+ return typeof frame === "number" ? { op, frame } : null;
10530
+ case "evaluate":
10531
+ return typeof frame === "number" && typeof expression === "string" ? { op, frame, expression } : null;
10532
+ default:
10533
+ return null;
10534
+ }
10535
+ }
10249
10536
  function errorText(error) {
10250
10537
  return (error instanceof Error ? error.message : String(error)).replaceAll(`
10251
10538
  `, " ");
@@ -10253,8 +10540,8 @@ function errorText(error) {
10253
10540
  async function listenForTheServer(end, debug) {
10254
10541
  let directory = null;
10255
10542
  try {
10256
- directory = process7.platform === "win32" ? null : mkdtempSync3(join7(tmpdir6(), "gdharness-keeper-"));
10257
- const address = directory === null ? `\\\\.\\pipe\\gdharness-keeper-${randomUUID()}` : join7(directory, "keeper.sock");
10543
+ directory = process7.platform === "win32" ? null : mkdtempSync3(join8(tmpdir6(), "gdharness-keeper-"));
10544
+ const address = directory === null ? `\\\\.\\pipe\\gdharness-keeper-${randomUUID()}` : join8(directory, "keeper.sock");
10258
10545
  const server = createServer2((socket) => {
10259
10546
  socket.setEncoding("utf8");
10260
10547
  socket.on("error", () => {});
@@ -10290,13 +10577,13 @@ async function listenForTheServer(end, debug) {
10290
10577
  close: () => {
10291
10578
  server.close();
10292
10579
  if (made !== null) {
10293
- rmSync5(made, { recursive: true, force: true });
10580
+ rmSync6(made, { recursive: true, force: true });
10294
10581
  }
10295
10582
  }
10296
10583
  };
10297
10584
  } catch {
10298
10585
  if (directory !== null) {
10299
- rmSync5(directory, { recursive: true, force: true });
10586
+ rmSync6(directory, { recursive: true, force: true });
10300
10587
  }
10301
10588
  return null;
10302
10589
  }
@@ -10312,7 +10599,9 @@ function listening(server, address) {
10312
10599
  }
10313
10600
 
10314
10601
  // src/outside.ts
10315
- import { fileURLToPath as fileURLToPath2 } from "node:url";
10602
+ import process8 from "node:process";
10603
+ import { text } from "node:stream/consumers";
10604
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
10316
10605
  function withChanges(base, changes) {
10317
10606
  const env = { ...base };
10318
10607
  for (const [name, value] of Object.entries(changes)) {
@@ -10328,13 +10617,70 @@ function sendSpec(child, spec) {
10328
10617
  child.stdin?.end(JSON.stringify(spec), "utf8");
10329
10618
  }
10330
10619
  async function receivedSpec() {
10331
- const chunks = [];
10332
- for await (const chunk of process.stdin) {
10333
- chunks.push(chunk);
10620
+ const received = await text(process8.stdin);
10621
+ const spec = sentSpecOf(JSON.parse(received));
10622
+ if (spec === null) {
10623
+ throw new Error(`What came down stdin is not a spec a server sends: ${received.slice(0, 200)}`);
10334
10624
  }
10335
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
10625
+ return spec;
10336
10626
  }
10337
- var KEEPER_SCRIPT = fileURLToPath2(new URL("./keeper.js", import.meta.url));
10627
+ function envChangesOf(value) {
10628
+ if (!isRecord(value)) {
10629
+ return null;
10630
+ }
10631
+ const changes = {};
10632
+ for (const [name, change] of Object.entries(value)) {
10633
+ if (typeof change !== "string" && change !== null) {
10634
+ return null;
10635
+ }
10636
+ changes[name] = change;
10637
+ }
10638
+ return changes;
10639
+ }
10640
+ function runOf(value) {
10641
+ if (!isRecord(value)) {
10642
+ return null;
10643
+ }
10644
+ const { transcript, startedAt, projectPath, servedBy, profile, breakpoints, debugged, slot } = value;
10645
+ const held = slot === undefined ? undefined : slotOf(slot);
10646
+ if (typeof transcript !== "string" || typeof startedAt !== "number" || typeof projectPath !== "string" || servedBy !== undefined && typeof servedBy !== "string" || profile !== undefined && typeof profile !== "string" || breakpoints !== undefined && !isStringList(breakpoints) || debugged !== undefined && typeof debugged !== "boolean" || held === null) {
10647
+ return null;
10648
+ }
10649
+ return {
10650
+ transcript,
10651
+ startedAt,
10652
+ projectPath,
10653
+ ...servedBy === undefined ? {} : { servedBy },
10654
+ ...profile === undefined ? {} : { profile },
10655
+ ...breakpoints === undefined ? {} : { breakpoints },
10656
+ ...debugged === undefined ? {} : { debugged },
10657
+ ...held === undefined ? {} : { slot: held }
10658
+ };
10659
+ }
10660
+ function slotOf(value) {
10661
+ const { lock, from, by } = isRecord(value) ? value : {};
10662
+ return typeof lock === "string" && typeof from === "number" && typeof by === "string" ? { lock, from, by } : null;
10663
+ }
10664
+ function sentSpecOf(value) {
10665
+ if (!isRecord(value)) {
10666
+ return null;
10667
+ }
10668
+ const { command, args, envChanges, run, desktop, noActivate } = value;
10669
+ const changes = envChangesOf(envChanges);
10670
+ const ran = run === undefined ? undefined : runOf(run);
10671
+ if (typeof command !== "string" || !isStringList(args) || changes === null || ran === null || desktop !== undefined && typeof desktop !== "string" || noActivate !== undefined && typeof noActivate !== "boolean") {
10672
+ return null;
10673
+ }
10674
+ return {
10675
+ command,
10676
+ args,
10677
+ envChanges: changes,
10678
+ ...ran === undefined ? {} : { run: ran },
10679
+ ...desktop === undefined ? {} : { desktop },
10680
+ ...noActivate === undefined ? {} : { noActivate }
10681
+ };
10682
+ }
10683
+ var KEEPER_SCRIPT = fileURLToPath3(new URL("./keeper.js", import.meta.url));
10338
10684
  function readLaunched(printed) {
10339
10685
  const line = printed.split(`
10340
10686
  `).find((one) => one.trim() !== "")?.trim() ?? "";
@@ -10347,12 +10693,12 @@ function readLaunched(printed) {
10347
10693
 
10348
10694
  // src/run-record.ts
10349
10695
  import { createHash as createHash2 } from "node:crypto";
10350
- import { mkdirSync as mkdirSync2, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync8, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
10351
- import { basename as basename4, join as join9, resolve as resolve4 } from "node:path";
10696
+ import { mkdirSync as mkdirSync2, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync9, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
10697
+ import { basename as basename4, join as join10, resolve as resolve5 } from "node:path";
10352
10698
 
10353
10699
  // src/runtime-client.ts
10354
10700
  import { tmpdir as tmpdir7 } from "node:os";
10355
- import { dirname as dirname4, join as join8, resolve as resolve3 } from "node:path";
10701
+ import { dirname as dirname4, join as join9, resolve as resolve4 } from "node:path";
10356
10702
 
10357
10703
  // src/launch.ts
10358
10704
  function envValue(name, variables = process.env) {
@@ -10397,7 +10743,7 @@ function runtimeDirectory(variables = process.env) {
10397
10743
  return explicit;
10398
10744
  }
10399
10745
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
10400
- return join8(perUser ?? tmpdir7(), "gdharness");
10746
+ return join9(perUser ?? tmpdir7(), "gdharness");
10401
10747
  }
10402
10748
  var ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
10403
10749
  var verdicts = new Map;
@@ -10407,15 +10753,15 @@ var untaken = new Map;
10407
10753
 
10408
10754
  // src/run-record.ts
10409
10755
  function runsDirectory() {
10410
- return join9(runtimeDirectory(), "runs");
10756
+ return join10(runtimeDirectory(), "runs");
10411
10757
  }
10412
10758
  function projectKey(projectPath) {
10413
- const real = realAsFarAsItExists(resolve4(projectPath)).replace(/[\\/]+$/, "");
10759
+ const real = realAsFarAsItExists(resolve5(projectPath)).replace(/[\\/]+$/, "");
10414
10760
  const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
10415
10761
  return createHash2("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
10416
10762
  }
10417
10763
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
10418
- return join9(directory, "runs", `note-${projectKey(projectPath)}.json`);
10764
+ return join10(directory, "runs", `note-${projectKey(projectPath)}.json`);
10419
10765
  }
10420
10766
  function writeRunRecord(record) {
10421
10767
  mkdirSync2(runsDirectory(), { recursive: true });
@@ -10443,11 +10789,11 @@ function recordRunEnded(projectPath, pid, ending) {
10443
10789
  function recordAt(path) {
10444
10790
  let parsed;
10445
10791
  try {
10446
- parsed = JSON.parse(readFileSync8(path, "utf8"));
10792
+ parsed = JSON.parse(readFileSync9(path, "utf8"));
10447
10793
  } catch {
10448
10794
  return null;
10449
10795
  }
10450
- if (!parsed || typeof parsed !== "object") {
10796
+ if (!isRecord(parsed)) {
10451
10797
  return null;
10452
10798
  }
10453
10799
  const fields = parsed;
@@ -10485,11 +10831,10 @@ function recordAt(path) {
10485
10831
  };
10486
10832
  }
10487
10833
  function restartedFromAt(value) {
10488
- if (!value || typeof value !== "object") {
10834
+ if (!isRecord(value)) {
10489
10835
  return [];
10490
10836
  }
10491
- const fields = value;
10492
- const { pid, endedAt, exitCode, exitSignal } = fields;
10837
+ const { pid, endedAt, exitCode, exitSignal } = value;
10493
10838
  if (typeof pid !== "number" || typeof endedAt !== "number") {
10494
10839
  return [];
10495
10840
  }
@@ -10863,9 +11208,9 @@ async function startDirectly(spec, output, hidden, detached = true, errors = out
10863
11208
  }
10864
11209
  async function startThroughHelper(spec, output, stopFile) {
10865
11210
  const scratch = scratchDirectory("desktop");
10866
- const pidFile = join10(scratch, "pid");
10867
- const errorFile = join10(scratch, "error");
10868
- const stepFile = join10(scratch, "step");
11211
+ const pidFile = join11(scratch, "pid");
11212
+ const errorFile = join11(scratch, "error");
11213
+ const stepFile = join11(scratch, "step");
10869
11214
  const where = spec.desktop !== undefined ? `on the ${spec.desktop} desktop` : spec.noActivate === true ? "without activation" : "through the desktop helper";
10870
11215
  try {
10871
11216
  const compiled = await ensureHelperCompiled();
@@ -10873,7 +11218,7 @@ async function startThroughHelper(spec, output, stopFile) {
10873
11218
  return { error: `it could not be started ${where}: ${compiled}` };
10874
11219
  }
10875
11220
  const wrapper = throughHelper({ ...spec, directory: process.cwd() }, spec.command, spec.args, {
10876
- spec: join10(scratch, "spec.json"),
11221
+ spec: join11(scratch, "spec.json"),
10877
11222
  pid: pidFile,
10878
11223
  error: errorFile,
10879
11224
  stop: stopFile,
@@ -10889,7 +11234,7 @@ async function startThroughHelper(spec, output, stopFile) {
10889
11234
  exited.now = true;
10890
11235
  });
10891
11236
  for (let waited = 0;waited < DESKTOP_START_MS; waited += 50) {
10892
- const pid = existsSync4(pidFile) ? Number(readFileSync9(pidFile, "utf8").trim()) : Number.NaN;
11237
+ const pid = existsSync5(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim()) : Number.NaN;
10893
11238
  if (Number.isInteger(pid) && pid > 0) {
10894
11239
  return { child: started.child, pid };
10895
11240
  }
@@ -10920,7 +11265,7 @@ function profiledBy(path, projectPath) {
10920
11265
  };
10921
11266
  let projectGodot = null;
10922
11267
  try {
10923
- projectGodot = readFileSync9(join10(projectPath, "project.godot"), "utf8");
11268
+ projectGodot = readFileSync10(join11(projectPath, "project.godot"), "utf8");
10924
11269
  } catch {}
10925
11270
  const observer = new ProfileObserver((totals) => {
10926
11271
  const first = latest === null;
@@ -10938,9 +11283,13 @@ function profiledBy(path, projectPath) {
10938
11283
  if (timer !== null) {
10939
11284
  clearTimeout(timer);
10940
11285
  }
10941
- latest = observer.totals() ?? latest;
11286
+ const last = observer.totals() ?? latest;
11287
+ latest = last;
11288
+ if (last === null) {
11289
+ return;
11290
+ }
10942
11291
  let tries = 0;
10943
- while (latest !== null && !writeProfileFile(path, { ...latest, connected: true }) && tries < LAST_WRITE_TRIES) {
11292
+ while (!writeProfileFile(path, { ...last, connected: true }) && tries < LAST_WRITE_TRIES) {
10944
11293
  tries += 1;
10945
11294
  }
10946
11295
  }
@@ -11021,12 +11370,12 @@ async function keep() {
11021
11370
  }
11022
11371
  const profiled = run?.profile === undefined ? null : profiledBy(run.profile, run.projectPath);
11023
11372
  const debuggerPeer = run === undefined || run.debugged === false ? null : await GameDebugger.open(run.breakpoints ?? [], profiled === null ? [] : [profiled.observer]);
11024
- const exitNotes = run === undefined ? null : mkdtempSync4(join10(tmpdir8(), "gdharness-exit-"));
11373
+ const exitNotes = run === undefined ? null : mkdtempSync4(join11(tmpdir8(), "gdharness-exit-"));
11025
11374
  const noted = exitNotes === null ? spec : { ...spec, envChanges: { ...spec.envChanges, [EXIT_NOTES_VARIABLE]: exitNotes } };
11026
11375
  const toStart = debuggerPeer === null ? noted : { ...noted, args: withEngineArguments(noted.args, debuggerPeer.engineArguments) };
11027
11376
  const transcript = run === undefined ? "ignore" : openSync4(run.transcript, "a");
11028
- const own = run !== undefined && throughTheHelper(spec) ? mkdtempSync4(join10(tmpdir8(), "gdharness-stop-")) : null;
11029
- const stopFile = own === null ? "" : join10(own, "stop");
11377
+ const own = run !== undefined && throughTheHelper(spec) ? mkdtempSync4(join11(tmpdir8(), "gdharness-stop-")) : null;
11378
+ const stopFile = own === null ? "" : join11(own, "stop");
11030
11379
  let started;
11031
11380
  const keeperStarted = run?.slot === undefined ? undefined : ownStart();
11032
11381
  const spawnedAt = Date.now();
@@ -11063,7 +11412,7 @@ async function keep() {
11063
11412
  let listening = null;
11064
11413
  let end = () => {
11065
11414
  if (stopFile !== "") {
11066
- writeFileSync4(stopFile, "");
11415
+ writeFileSync5(stopFile, "");
11067
11416
  return "signalled";
11068
11417
  }
11069
11418
  return child.kill() ? "signalled" : "gone";
@@ -11165,7 +11514,7 @@ async function keep() {
11165
11514
  listening?.close();
11166
11515
  for (const made of [own, exitNotes]) {
11167
11516
  if (made !== null) {
11168
- rmSync7(made, { recursive: true, force: true });
11517
+ rmSync8(made, { recursive: true, force: true });
11169
11518
  }
11170
11519
  }
11171
11520
  process.exit(0);
@@ -11176,7 +11525,7 @@ function restartSaid(notes, pid) {
11176
11525
  return null;
11177
11526
  }
11178
11527
  try {
11179
- const said = JSON.parse(readFileSync9(join10(notes, `exit-${String(pid)}.json`), "utf8"));
11528
+ const said = JSON.parse(readFileSync10(join11(notes, `exit-${String(pid)}.json`), "utf8"));
11180
11529
  return typeof said === "object" && said !== null && "restart" in said && typeof said.restart === "boolean" ? said.restart : null;
11181
11530
  } catch {
11182
11531
  return null;