gdharness 1.3.59 → 1.3.61

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 +2264 -1706
  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 +18 -11
  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 +13 -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 +18 -11
  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 +28 -10
  76. package/build/keeper.js +511 -161
  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 existsSync3, 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,11 +7671,12 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7435
7671
  // src/engine-slots.ts
7436
7672
  import {
7437
7673
  closeSync as closeSync2,
7674
+ existsSync as existsSync3,
7438
7675
  mkdirSync,
7439
7676
  openSync as openSync2,
7440
- readdirSync,
7441
- readFileSync as readFileSync5,
7442
- rmSync as rmSync3,
7677
+ readdirSync as readdirSync2,
7678
+ readFileSync as readFileSync6,
7679
+ rmSync as rmSync4,
7443
7680
  statSync as statSync3,
7444
7681
  watch,
7445
7682
  writeSync as writeSync2
@@ -7485,18 +7722,19 @@ var buffered = "";
7485
7722
  var next = 1;
7486
7723
  var out = null;
7487
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
+ }
7488
7731
  var held = false;
7489
7732
  function holding(child, wanted) {
7490
7733
  if (held === wanted) {
7491
7734
  return;
7492
7735
  }
7493
7736
  held = wanted;
7494
- const stdout = child.stdout;
7495
- if (wanted) {
7496
- stdout.ref?.();
7497
- } else {
7498
- stdout.unref?.();
7499
- }
7737
+ holdOn(child.stdout, wanted);
7500
7738
  }
7501
7739
  function running() {
7502
7740
  if (helper?.exitCode === null && helper.signalCode === null) {
@@ -7538,8 +7776,8 @@ function running() {
7538
7776
  child.once("exit", gone);
7539
7777
  child.once("error", gone);
7540
7778
  child.unref();
7541
- child.stdin.unref?.();
7542
- child.stderr.unref?.();
7779
+ holdOn(child.stdin, false);
7780
+ holdOn(child.stderr, false);
7543
7781
  held = true;
7544
7782
  holding(child, false);
7545
7783
  helper = child;
@@ -7968,14 +8206,14 @@ async function loadingTheTable() {
7968
8206
  return `$null = [Reflection.Assembly]::LoadFrom('${TABLE.assembly().replaceAll("'", "''")}')`;
7969
8207
  }
7970
8208
  async function askProcessTable(call, withinMs) {
7971
- return askWindows(`${await loadingTheTable()}; [GdhProcessTable]::${call}`, withinMs);
8209
+ return await askWindows(`${await loadingTheTable()}; [GdhProcessTable]::${call}`, withinMs);
7972
8210
  }
7973
8211
 
7974
8212
  // src/engine-spent.ts
7975
8213
  var watched = new Set;
7976
8214
 
7977
8215
  // src/harnesses.ts
7978
- 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";
7979
8217
 
7980
8218
  // node_modules/yaml/dist/index.js
7981
8219
  var composer = require_composer();
@@ -8025,13 +8263,13 @@ var $visitAsync = visit.visitAsync;
8025
8263
 
8026
8264
  // src/paths.ts
8027
8265
  import { realpathSync, statSync as statSync2 } from "node:fs";
8028
- 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";
8029
8267
  function realAsFarAsItExists(path) {
8030
8268
  const missing = [];
8031
8269
  let at = path;
8032
8270
  for (;; ) {
8033
8271
  try {
8034
- return join2(realpathSync.native(at), ...missing);
8272
+ return join3(realpathSync.native(at), ...missing);
8035
8273
  } catch {
8036
8274
  const parent = dirname(at);
8037
8275
  if (parent === at) {
@@ -8043,6 +8281,14 @@ function realAsFarAsItExists(path) {
8043
8281
  }
8044
8282
  }
8045
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
+
8046
8292
  // src/harnesses.ts
8047
8293
  var SERVER_KEY = "gdharness";
8048
8294
  function tomlArray(values) {
@@ -8065,7 +8311,7 @@ var HARNESSES = [
8065
8311
  shape: "plain",
8066
8312
  home: ".claude",
8067
8313
  marker: ".claude",
8068
- skills: { dir: join3(".claude", "skills"), shared: false }
8314
+ skills: { dir: join4(".claude", "skills"), shared: false }
8069
8315
  },
8070
8316
  {
8071
8317
  id: "copilot-cli",
@@ -8075,7 +8321,7 @@ var HARNESSES = [
8075
8321
  container: "mcpServers",
8076
8322
  shape: "plain",
8077
8323
  home: ".copilot",
8078
- skills: { dir: join3(".github", "skills"), shared: true },
8324
+ skills: { dir: join4(".github", "skills"), shared: true },
8079
8325
  manual: "Trust the folder when Copilot CLI first asks, or it reads no config here."
8080
8326
  },
8081
8327
  {
@@ -8101,22 +8347,22 @@ var HARNESSES = [
8101
8347
  id: "cursor",
8102
8348
  name: "Cursor",
8103
8349
  scope: "project",
8104
- file: join3(".cursor", "mcp.json"),
8350
+ file: join4(".cursor", "mcp.json"),
8105
8351
  container: "mcpServers",
8106
8352
  shape: "plain",
8107
8353
  home: ".cursor",
8108
- skills: { dir: join3(".cursor", "skills"), shared: true },
8354
+ skills: { dir: join4(".cursor", "skills"), shared: true },
8109
8355
  manual: "Cursor asks before each tool call by default, and the server can be toggled off."
8110
8356
  },
8111
8357
  {
8112
8358
  id: "vscode",
8113
8359
  name: "VS Code",
8114
8360
  scope: "project",
8115
- file: join3(".vscode", "mcp.json"),
8361
+ file: join4(".vscode", "mcp.json"),
8116
8362
  container: "servers",
8117
8363
  shape: "typed",
8118
8364
  home: ".vscode",
8119
- skills: { dir: join3(".github", "skills"), shared: true },
8365
+ skills: { dir: join4(".github", "skills"), shared: true },
8120
8366
  manual: "VS Code asks you to trust a server the first time it starts one."
8121
8367
  },
8122
8368
  {
@@ -8126,45 +8372,45 @@ var HARNESSES = [
8126
8372
  file: "opencode.json",
8127
8373
  container: "mcp",
8128
8374
  shape: "opencode",
8129
- home: join3(".config", "opencode"),
8375
+ home: join4(".config", "opencode"),
8130
8376
  marker: ".opencode",
8131
- skills: { dir: join3(".opencode", "skills"), shared: true }
8377
+ skills: { dir: join4(".opencode", "skills"), shared: true }
8132
8378
  },
8133
8379
  {
8134
8380
  id: "junie",
8135
8381
  name: "Junie",
8136
8382
  scope: "project",
8137
- file: join3(".junie", "mcp", "mcp.json"),
8383
+ file: join4(".junie", "mcp", "mcp.json"),
8138
8384
  container: "mcpServers",
8139
8385
  shape: "plain",
8140
8386
  home: ".junie",
8141
- skills: { dir: join3(".junie", "skills"), shared: true }
8387
+ skills: { dir: join4(".junie", "skills"), shared: true }
8142
8388
  },
8143
8389
  {
8144
8390
  id: "kiro",
8145
8391
  name: "Kiro",
8146
8392
  scope: "project",
8147
- file: join3(".kiro", "settings", "mcp.json"),
8393
+ file: join4(".kiro", "settings", "mcp.json"),
8148
8394
  container: "mcpServers",
8149
8395
  shape: "plain",
8150
8396
  home: ".kiro",
8151
- skills: { dir: join3(".kiro", "skills"), shared: false }
8397
+ skills: { dir: join4(".kiro", "skills"), shared: false }
8152
8398
  },
8153
8399
  {
8154
8400
  id: "gemini",
8155
8401
  name: "Gemini CLI",
8156
8402
  scope: "project",
8157
- file: join3(".gemini", "settings.json"),
8403
+ file: join4(".gemini", "settings.json"),
8158
8404
  container: "mcpServers",
8159
8405
  shape: "plain",
8160
8406
  home: ".gemini",
8161
- skills: { dir: join3(".gemini", "skills"), shared: true }
8407
+ skills: { dir: join4(".gemini", "skills"), shared: true }
8162
8408
  },
8163
8409
  {
8164
8410
  id: "roo",
8165
8411
  name: "Roo Code",
8166
8412
  scope: "project",
8167
- file: join3(".roo", "mcp.json"),
8413
+ file: join4(".roo", "mcp.json"),
8168
8414
  container: "mcpServers",
8169
8415
  shape: "plain"
8170
8416
  },
@@ -8172,7 +8418,7 @@ var HARNESSES = [
8172
8418
  id: "kilo",
8173
8419
  name: "Kilo Code",
8174
8420
  scope: "project",
8175
- file: join3(".kilocode", "mcp.json"),
8421
+ file: join4(".kilocode", "mcp.json"),
8176
8422
  container: "mcpServers",
8177
8423
  shape: "plain"
8178
8424
  },
@@ -8180,16 +8426,16 @@ var HARNESSES = [
8180
8426
  id: "amazon-q",
8181
8427
  name: "Amazon Q Developer CLI",
8182
8428
  scope: "project",
8183
- file: join3(".amazonq", "mcp.json"),
8429
+ file: join4(".amazonq", "mcp.json"),
8184
8430
  container: "mcpServers",
8185
8431
  shape: "plain",
8186
- home: join3(".aws", "amazonq")
8432
+ home: join4(".aws", "amazonq")
8187
8433
  },
8188
8434
  {
8189
8435
  id: "zed",
8190
8436
  name: "Zed",
8191
8437
  scope: "project",
8192
- file: join3(".zed", "settings.json"),
8438
+ file: join4(".zed", "settings.json"),
8193
8439
  container: "context_servers",
8194
8440
  shape: "plain"
8195
8441
  },
@@ -8197,16 +8443,16 @@ var HARNESSES = [
8197
8443
  id: "amp",
8198
8444
  name: "Amp",
8199
8445
  scope: "project",
8200
- file: join3(".amp", "settings.json"),
8446
+ file: join4(".amp", "settings.json"),
8201
8447
  container: "amp.mcpServers",
8202
8448
  shape: "plain",
8203
- home: join3(".config", "amp")
8449
+ home: join4(".config", "amp")
8204
8450
  },
8205
8451
  {
8206
8452
  id: "warp",
8207
8453
  name: "Warp",
8208
8454
  scope: "project",
8209
- file: join3(".warp", ".mcp.json"),
8455
+ file: join4(".warp", ".mcp.json"),
8210
8456
  container: "mcpServers",
8211
8457
  shape: "plain",
8212
8458
  home: ".warp",
@@ -8216,7 +8462,7 @@ var HARNESSES = [
8216
8462
  id: "trae",
8217
8463
  name: "Trae",
8218
8464
  scope: "project",
8219
- file: join3(".trae", "mcp.json"),
8465
+ file: join4(".trae", "mcp.json"),
8220
8466
  container: "mcpServers",
8221
8467
  shape: "plain",
8222
8468
  home: ".trae"
@@ -8225,7 +8471,7 @@ var HARNESSES = [
8225
8471
  id: "factory",
8226
8472
  name: "Factory Droid",
8227
8473
  scope: "project",
8228
- file: join3(".factory", "mcp.json"),
8474
+ file: join4(".factory", "mcp.json"),
8229
8475
  container: "mcpServers",
8230
8476
  shape: "typed",
8231
8477
  home: ".factory"
@@ -8234,7 +8480,7 @@ var HARNESSES = [
8234
8480
  id: "tabnine",
8235
8481
  name: "Tabnine",
8236
8482
  scope: "project",
8237
- file: join3(".tabnine", "mcp_servers.json"),
8483
+ file: join4(".tabnine", "mcp_servers.json"),
8238
8484
  container: "mcpServers",
8239
8485
  shape: "plain",
8240
8486
  home: ".tabnine"
@@ -8251,7 +8497,7 @@ var HARNESSES = [
8251
8497
  id: "pi",
8252
8498
  name: "pi",
8253
8499
  scope: "project",
8254
- file: join3(".pi", "mcp.json"),
8500
+ file: join4(".pi", "mcp.json"),
8255
8501
  container: "mcpServers",
8256
8502
  shape: "plain",
8257
8503
  home: ".pi"
@@ -8260,7 +8506,7 @@ var HARNESSES = [
8260
8506
  id: "mistral-vibe",
8261
8507
  name: "Mistral Vibe",
8262
8508
  scope: "project",
8263
- file: join3(".vibe", "config.toml"),
8509
+ file: join4(".vibe", "config.toml"),
8264
8510
  container: "mcp_servers",
8265
8511
  shape: "plain",
8266
8512
  toml: {
@@ -8316,7 +8562,7 @@ var HARNESSES = [
8316
8562
  id: "codex",
8317
8563
  name: "Codex CLI",
8318
8564
  scope: "home",
8319
- file: join3(".codex", "config.toml"),
8565
+ file: join4(".codex", "config.toml"),
8320
8566
  container: "mcp_servers",
8321
8567
  shape: "plain",
8322
8568
  manual: "Codex reads its config at startup, so restart it.",
@@ -8335,20 +8581,20 @@ var HARNESSES = [
8335
8581
  id: "cline",
8336
8582
  name: "Cline",
8337
8583
  scope: "home",
8338
- file: join3(".cline", "data", "settings", "cline_mcp_settings.json"),
8584
+ file: join4(".cline", "data", "settings", "cline_mcp_settings.json"),
8339
8585
  container: "mcpServers",
8340
8586
  shape: "plain",
8341
8587
  marker: ".cline",
8342
- skills: { dir: join3(".cline", "skills"), shared: false }
8588
+ skills: { dir: join4(".cline", "skills"), shared: false }
8343
8589
  },
8344
8590
  {
8345
8591
  id: "goose",
8346
8592
  name: "Goose",
8347
8593
  scope: "home",
8348
8594
  file: {
8349
- win32: { appData: join3("Block", "goose", "config", "config.yaml") },
8350
- darwin: join3(".config", "goose", "config.yaml"),
8351
- 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")
8352
8598
  },
8353
8599
  container: "extensions",
8354
8600
  shape: "plain",
@@ -8369,19 +8615,19 @@ var HARNESSES = [
8369
8615
  id: "windsurf",
8370
8616
  name: "Windsurf",
8371
8617
  scope: "home",
8372
- file: join3(".codeium", "windsurf", "mcp_config.json"),
8618
+ file: join4(".codeium", "windsurf", "mcp_config.json"),
8373
8619
  container: "mcpServers",
8374
8620
  shape: "plain",
8375
- skills: { dir: join3(".windsurf", "skills"), shared: true }
8621
+ skills: { dir: join4(".windsurf", "skills"), shared: true }
8376
8622
  },
8377
8623
  {
8378
8624
  id: "hermes",
8379
8625
  name: "Hermes",
8380
8626
  scope: "home",
8381
- file: join3(".hermes", "config.yaml"),
8627
+ file: join4(".hermes", "config.yaml"),
8382
8628
  container: "mcp_servers",
8383
8629
  shape: "plain",
8384
- skills: { dir: join3(".hermes", "skills"), shared: true },
8630
+ skills: { dir: join4(".hermes", "skills"), shared: true },
8385
8631
  yaml: {
8386
8632
  path: ["mcp_servers", SERVER_KEY],
8387
8633
  entry: (launch) => ({
@@ -8396,7 +8642,7 @@ var HARNESSES = [
8396
8642
  id: "openclaw",
8397
8643
  name: "OpenClaw",
8398
8644
  scope: "home",
8399
- file: join3(".openclaw", "openclaw.json"),
8645
+ file: join4(".openclaw", "openclaw.json"),
8400
8646
  container: "mcpServers",
8401
8647
  shape: "plain"
8402
8648
  },
@@ -8405,9 +8651,9 @@ var HARNESSES = [
8405
8651
  name: "Claude Desktop",
8406
8652
  scope: "home",
8407
8653
  file: {
8408
- win32: { appData: join3("Claude", "claude_desktop_config.json") },
8409
- darwin: join3("Library", "Application Support", "Claude", "claude_desktop_config.json"),
8410
- 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")
8411
8657
  },
8412
8658
  container: "mcpServers",
8413
8659
  shape: "plain"
@@ -8416,7 +8662,7 @@ var HARNESSES = [
8416
8662
  id: "zeroclaw",
8417
8663
  name: "ZeroClaw",
8418
8664
  scope: "home",
8419
- file: join3(".zeroclaw", "config.toml"),
8665
+ file: join4(".zeroclaw", "config.toml"),
8420
8666
  container: "mcp.servers",
8421
8667
  shape: "plain",
8422
8668
  toml: {
@@ -8437,7 +8683,7 @@ var HARNESSES = [
8437
8683
  id: "deepcode",
8438
8684
  name: "Deep Code",
8439
8685
  scope: "home",
8440
- file: join3(".deepcode", "settings.json"),
8686
+ file: join4(".deepcode", "settings.json"),
8441
8687
  container: "mcpServers",
8442
8688
  shape: "plain"
8443
8689
  },
@@ -8445,7 +8691,7 @@ var HARNESSES = [
8445
8691
  id: "nanobot",
8446
8692
  name: "nanobot",
8447
8693
  scope: "home",
8448
- file: join3(".nanobot", "config.json"),
8694
+ file: join4(".nanobot", "config.json"),
8449
8695
  container: "mcpServers",
8450
8696
  shape: "plain",
8451
8697
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
@@ -8454,7 +8700,7 @@ var HARNESSES = [
8454
8700
  id: "autohand",
8455
8701
  name: "Autohand",
8456
8702
  scope: "home",
8457
- file: join3(".autohand", "config.json"),
8703
+ file: join4(".autohand", "config.json"),
8458
8704
  container: "mcpServers",
8459
8705
  shape: "plain",
8460
8706
  snippet: (launch) => jsonSnippet(launch, "mcpServers", "plain")
@@ -8463,7 +8709,7 @@ var HARNESSES = [
8463
8709
  var OURS = ["GODOT_PATH", "GDHARNESS_PROJECT"];
8464
8710
  function environmentFor(launch, held) {
8465
8711
  const kept = {};
8466
- if (typeof held === "object" && held !== null && !Array.isArray(held)) {
8712
+ if (isRecord(held)) {
8467
8713
  for (const [name, value] of Object.entries(held)) {
8468
8714
  if (typeof value === "string" && !OURS.includes(name)) {
8469
8715
  kept[name] = value;
@@ -8473,7 +8719,7 @@ function environmentFor(launch, held) {
8473
8719
  return { ...kept, ...launch.env };
8474
8720
  }
8475
8721
  function entryFor(shape, launch, held) {
8476
- const before = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
8722
+ const before = isRecord(held) ? { ...held } : {};
8477
8723
  if (shape === "opencode") {
8478
8724
  return {
8479
8725
  ...before,
@@ -8499,7 +8745,15 @@ var LOG_LIMIT_BYTES = 128 * 1024 * 1024;
8499
8745
  var DECLARING = new Set(["var", "const", "func", "signal", "enum", "class", "for", "class_name"]);
8500
8746
  var NAME = "[\\p{L}_][\\p{L}\\p{N}_]*";
8501
8747
  var ANNOTATIONS = "(?:@[\\p{L}_][\\p{L}\\p{N}_]*(?:\\([^)]*\\))?\\s+)*";
8502
- 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");
8503
8757
  var CLASS_NAME_LINE = new RegExp(`^${ANNOTATIONS}class_name\\s+(${NAME})`, "u");
8504
8758
  var EXTENDS_LINE = new RegExp(`^${ANNOTATIONS}(?:class_name\\s+${NAME}\\s+)?extends\\s`, "u");
8505
8759
  var FUNC_LINE = new RegExp(`^[ \\t]*${ANNOTATIONS}(?:static\\s+)?func\\s+(${NAME})\\s*\\(`, "u");
@@ -8553,7 +8807,7 @@ var KEYWORDS = new Set([
8553
8807
  ]);
8554
8808
 
8555
8809
  // src/whole-file.ts
8556
- 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";
8557
8811
  var HELD_FILE_MS = 1000;
8558
8812
  var HELD_FILE_RETRY_MS = 10;
8559
8813
  var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
@@ -8561,9 +8815,9 @@ function readWhole(path) {
8561
8815
  const until = Date.now() + HELD_FILE_MS;
8562
8816
  for (;; ) {
8563
8817
  try {
8564
- return readFileSync3(path, "utf8");
8818
+ return readFileSync4(path, "utf8");
8565
8819
  } catch (error) {
8566
- const code = error.code ?? "";
8820
+ const code = errorCode(error) ?? "";
8567
8821
  if (code === "ENOENT") {
8568
8822
  return null;
8569
8823
  }
@@ -8577,20 +8831,20 @@ function readWhole(path) {
8577
8831
  function writeWhole(path, text) {
8578
8832
  const partial = `${path}.${process.pid}.partial`;
8579
8833
  try {
8580
- writeFileSync3(partial, text, "utf8");
8834
+ writeFileSync4(partial, text, "utf8");
8581
8835
  } catch {
8582
8836
  return false;
8583
8837
  }
8584
8838
  const until = Date.now() + HELD_FILE_MS;
8585
8839
  for (;; ) {
8586
8840
  try {
8587
- renameSync(partial, path);
8841
+ renameSync2(partial, path);
8588
8842
  return true;
8589
8843
  } catch (error) {
8590
- const code = error.code ?? "";
8844
+ const code = errorCode(error) ?? "";
8591
8845
  if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
8592
8846
  try {
8593
- rmSync2(partial, { force: true });
8847
+ rmSync3(partial, { force: true });
8594
8848
  } catch {}
8595
8849
  return false;
8596
8850
  }
@@ -8602,10 +8856,10 @@ function removeWhole(path) {
8602
8856
  const until = Date.now() + HELD_FILE_MS;
8603
8857
  for (;; ) {
8604
8858
  try {
8605
- rmSync2(path, { force: true });
8859
+ rmSync3(path, { force: true });
8606
8860
  return true;
8607
8861
  } catch (error) {
8608
- const code = error.code ?? "";
8862
+ const code = errorCode(error) ?? "";
8609
8863
  if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
8610
8864
  return false;
8611
8865
  }
@@ -8615,7 +8869,7 @@ function removeWhole(path) {
8615
8869
  }
8616
8870
 
8617
8871
  // src/engine-leaks.ts
8618
- 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.";
8619
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.";
8620
8874
  var NAME2 = "[\\p{L}_][\\p{L}\\p{N}_]*";
8621
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");
@@ -8668,7 +8922,7 @@ var MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
8668
8922
  var SCAN_BYTES = 1024 * 1024;
8669
8923
 
8670
8924
  // src/process-children.ts
8671
- import { readFileSync as readFileSync4 } from "node:fs";
8925
+ import { readFileSync as readFileSync5 } from "node:fs";
8672
8926
  import { basename as basename3 } from "node:path";
8673
8927
  async function startTimesOf(pids) {
8674
8928
  const began = new Map;
@@ -8710,7 +8964,7 @@ async function linuxStartTimes(pids) {
8710
8964
  const hertz = await ticksPerSecond();
8711
8965
  let booted;
8712
8966
  try {
8713
- 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;
8714
8968
  } catch {
8715
8969
  return began;
8716
8970
  }
@@ -8720,7 +8974,7 @@ async function linuxStartTimes(pids) {
8720
8974
  for (const pid of pids) {
8721
8975
  let stat;
8722
8976
  try {
8723
- stat = readFileSync4(`/proc/${String(pid)}/stat`, "utf8");
8977
+ stat = readFileSync5(`/proc/${String(pid)}/stat`, "utf8");
8724
8978
  } catch {
8725
8979
  continue;
8726
8980
  }
@@ -8750,7 +9004,7 @@ async function ticksPerSecond() {
8750
9004
  }
8751
9005
  function printedBy(command, args, env) {
8752
9006
  return runHidden(command, args, { timeout: ASK_TIMEOUT_MS, env }).then(({ stdout }) => stdout, (error) => {
8753
- const printed = error.stdout;
9007
+ const printed = error instanceof Error && "stdout" in error ? error.stdout : undefined;
8754
9008
  return typeof printed === "string" ? printed : "";
8755
9009
  });
8756
9010
  }
@@ -8882,7 +9136,7 @@ function nameSlotEngine(lock, holder, engine) {
8882
9136
  }
8883
9137
  function giveBackSlot(lock, pid) {
8884
9138
  try {
8885
- if (holderOf(readFileSync5(lock, "utf8"))?.pid === pid) {
9139
+ if (holderOf(readFileSync6(lock, "utf8"))?.pid === pid) {
8886
9140
  removeWhole(lock);
8887
9141
  }
8888
9142
  } catch {}
@@ -8916,9 +9170,9 @@ function crashesReported() {
8916
9170
  }
8917
9171
 
8918
9172
  // src/game-debugger.ts
8919
- import { readFileSync as readFileSync7 } from "node:fs";
9173
+ import { readFileSync as readFileSync8 } from "node:fs";
8920
9174
  import { createServer } from "node:net";
8921
- import { join as join6 } from "node:path";
9175
+ import { join as join7 } from "node:path";
8922
9176
 
8923
9177
  // src/godot-variant.ts
8924
9178
  var NIL = 0;
@@ -9223,8 +9477,8 @@ function readMessage(body) {
9223
9477
  }
9224
9478
 
9225
9479
  // src/setup.ts
9226
- import { dirname as dirname3, join as join5 } from "node:path";
9227
- import { fileURLToPath } from "node:url";
9480
+ import { dirname as dirname3, join as join6 } from "node:path";
9481
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
9228
9482
 
9229
9483
  // src/dictionary.ts
9230
9484
  function emptyRecord() {
@@ -9338,25 +9592,25 @@ var IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
9338
9592
  var hashes = new Map;
9339
9593
 
9340
9594
  // src/scratch.ts
9341
- import { existsSync as existsSync2, 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";
9342
9596
  import { tmpdir as tmpdir5 } from "node:os";
9343
- import { join as join4 } from "node:path";
9597
+ import { join as join5 } from "node:path";
9344
9598
  import process6 from "node:process";
9345
9599
  var SWEPT = { recursive: true, force: true };
9346
9600
  var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
9347
9601
  var waiting = new Set;
9348
9602
  var atExit = new Set;
9349
9603
  function scratchDirectory(kind) {
9350
- return mkdtempSync2(join4(tmpdir5(), `gdharness-${kind}-${process6.pid}-`));
9604
+ return mkdtempSync2(join5(tmpdir5(), `gdharness-${kind}-${process6.pid}-`));
9351
9605
  }
9352
- function discardWith(remove, paths) {
9606
+ function discardWith(removePath, paths) {
9353
9607
  const left = [];
9354
9608
  for (const path of paths) {
9355
9609
  if (!path) {
9356
9610
  continue;
9357
9611
  }
9358
9612
  try {
9359
- remove(path);
9613
+ removePath(path);
9360
9614
  } catch (error) {
9361
9615
  left.push({ path, reason: error instanceof Error ? error.message : String(error) });
9362
9616
  }
@@ -9371,7 +9625,7 @@ function discard(...paths) {
9371
9625
  return left;
9372
9626
  }
9373
9627
  function remove(path) {
9374
- rmSync4(path, SWEPT);
9628
+ rmSync5(path, SWEPT);
9375
9629
  }
9376
9630
  function tryAgain(path, attempt) {
9377
9631
  if (attempt === 0) {
@@ -9383,7 +9637,7 @@ function tryAgain(path, attempt) {
9383
9637
  lastTryOnExit();
9384
9638
  }
9385
9639
  const after = AGAIN_AFTER_MS[attempt];
9386
- if (after === undefined || !existsSync2(path)) {
9640
+ if (after === undefined || !existsSync4(path)) {
9387
9641
  waiting.delete(path);
9388
9642
  if (after === undefined) {
9389
9643
  atExit.add(path);
@@ -9532,7 +9786,7 @@ function parseIniLikeValue(value) {
9532
9786
  }
9533
9787
 
9534
9788
  // src/server-version.ts
9535
- import { readFileSync as readFileSync6 } from "node:fs";
9789
+ import { readFileSync as readFileSync7 } from "node:fs";
9536
9790
 
9537
9791
  // src/update-check.ts
9538
9792
  var CACHE_MS = 10 * 60 * 1000;
@@ -9543,8 +9797,9 @@ var MAX_RETRY_MS = 60 * 60 * 1000;
9543
9797
  var DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
9544
9798
  var SERVER_VERSION = (() => {
9545
9799
  try {
9546
- const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
9547
- 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";
9548
9803
  } catch {
9549
9804
  return "0.0.0";
9550
9805
  }
@@ -9556,7 +9811,7 @@ var RUNTIME_AUTOLOAD = {
9556
9811
  path: "gdharness/loader.gd"
9557
9812
  };
9558
9813
  function shippedAddonsDirectory() {
9559
- return join5(dirname3(fileURLToPath(import.meta.url)), "godot", "addons");
9814
+ return join6(dirname3(fileURLToPath2(import.meta.url)), "godot", "addons");
9560
9815
  }
9561
9816
 
9562
9817
  // src/game-debugger.ts
@@ -9567,9 +9822,12 @@ var ADDON_LINES = 200;
9567
9822
  var RUNTIME_ADDON = "res://addons/gdharness_runtime/";
9568
9823
  var RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON}runtime_autoload.gd`;
9569
9824
  var frameEnd = null;
9825
+ function topIsTheAddon(frames) {
9826
+ return frames[0]?.script.startsWith(RUNTIME_ADDON) ?? false;
9827
+ }
9570
9828
  function frameEndLine() {
9571
9829
  if (frameEnd === null) {
9572
- 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(`
9573
9831
  `);
9574
9832
  const declared = lines.findIndex((line) => line.startsWith("func _notification("));
9575
9833
  if (declared === -1) {
@@ -9969,7 +10227,7 @@ class GameDebugger {
9969
10227
  if (entered === null) {
9970
10228
  return {
9971
10229
  paused: false,
9972
- 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.`
9973
10231
  };
9974
10232
  }
9975
10233
  }
@@ -9984,7 +10242,7 @@ class GameDebugger {
9984
10242
  if (again === null) {
9985
10243
  return {
9986
10244
  paused: false,
9987
- 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.`
9988
10246
  };
9989
10247
  }
9990
10248
  }
@@ -9992,7 +10250,7 @@ class GameDebugger {
9992
10250
  if (past === "lost") {
9993
10251
  return {
9994
10252
  paused: false,
9995
- 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.`
9996
10254
  };
9997
10255
  }
9998
10256
  this.stoppedBy("Paused");
@@ -10012,7 +10270,7 @@ class GameDebugger {
10012
10270
  let frames = await this.stack();
10013
10271
  let framesLeft = ADDON_FRAMES;
10014
10272
  let linesLeft = ADDON_LINES;
10015
- while (frames !== null && this.held !== null && (frames[0]?.script.startsWith(RUNTIME_ADDON) ?? false)) {
10273
+ while (frames !== null && this.held !== null && topIsTheAddon(frames)) {
10016
10274
  if (until !== undefined && Date.now() >= until) {
10017
10275
  return { frames, gaveUp: "time" };
10018
10276
  }
@@ -10092,7 +10350,7 @@ class GameDebugger {
10092
10350
  return {
10093
10351
  stepped: true,
10094
10352
  heldAgain: false,
10095
- 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.`
10096
10354
  };
10097
10355
  }
10098
10356
  this.stoppedBy("Stepped");
@@ -10210,7 +10468,7 @@ class GameDebugger {
10210
10468
  return null;
10211
10469
  }
10212
10470
  return {
10213
- 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(", ")}.`
10214
10472
  };
10215
10473
  }
10216
10474
  close() {
@@ -10221,10 +10479,10 @@ class GameDebugger {
10221
10479
 
10222
10480
  // src/keeper-channel.ts
10223
10481
  import { randomUUID } from "node:crypto";
10224
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync5 } from "node:fs";
10482
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync6 } from "node:fs";
10225
10483
  import { connect, createServer as createServer2 } from "node:net";
10226
10484
  import { tmpdir as tmpdir6 } from "node:os";
10227
- import { join as join7 } from "node:path";
10485
+ import { join as join8 } from "node:path";
10228
10486
  import process7 from "node:process";
10229
10487
  var DEBUG_PREFIX = "debug ";
10230
10488
  function endedOrWhyNot(end) {
@@ -10240,11 +10498,41 @@ async function debuggedOrWhyNot(debug, asked) {
10240
10498
  return JSON.stringify({ refused: "This run has no debugger." });
10241
10499
  }
10242
10500
  try {
10243
- 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));
10244
10506
  } catch (error) {
10245
10507
  return JSON.stringify({ refused: `The debugger could not answer: ${errorText(error)}` });
10246
10508
  }
10247
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
+ }
10248
10536
  function errorText(error) {
10249
10537
  return (error instanceof Error ? error.message : String(error)).replaceAll(`
10250
10538
  `, " ");
@@ -10252,8 +10540,8 @@ function errorText(error) {
10252
10540
  async function listenForTheServer(end, debug) {
10253
10541
  let directory = null;
10254
10542
  try {
10255
- directory = process7.platform === "win32" ? null : mkdtempSync3(join7(tmpdir6(), "gdharness-keeper-"));
10256
- 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");
10257
10545
  const server = createServer2((socket) => {
10258
10546
  socket.setEncoding("utf8");
10259
10547
  socket.on("error", () => {});
@@ -10289,13 +10577,13 @@ async function listenForTheServer(end, debug) {
10289
10577
  close: () => {
10290
10578
  server.close();
10291
10579
  if (made !== null) {
10292
- rmSync5(made, { recursive: true, force: true });
10580
+ rmSync6(made, { recursive: true, force: true });
10293
10581
  }
10294
10582
  }
10295
10583
  };
10296
10584
  } catch {
10297
10585
  if (directory !== null) {
10298
- rmSync5(directory, { recursive: true, force: true });
10586
+ rmSync6(directory, { recursive: true, force: true });
10299
10587
  }
10300
10588
  return null;
10301
10589
  }
@@ -10311,7 +10599,9 @@ function listening(server, address) {
10311
10599
  }
10312
10600
 
10313
10601
  // src/outside.ts
10314
- 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";
10315
10605
  function withChanges(base, changes) {
10316
10606
  const env = { ...base };
10317
10607
  for (const [name, value] of Object.entries(changes)) {
@@ -10327,13 +10617,70 @@ function sendSpec(child, spec) {
10327
10617
  child.stdin?.end(JSON.stringify(spec), "utf8");
10328
10618
  }
10329
10619
  async function receivedSpec() {
10330
- const chunks = [];
10331
- for await (const chunk of process.stdin) {
10332
- 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)}`);
10624
+ }
10625
+ return spec;
10626
+ }
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;
10333
10643
  }
10334
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
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
+ };
10335
10682
  }
10336
- var KEEPER_SCRIPT = fileURLToPath2(new URL("./keeper.js", import.meta.url));
10683
+ var KEEPER_SCRIPT = fileURLToPath3(new URL("./keeper.js", import.meta.url));
10337
10684
  function readLaunched(printed) {
10338
10685
  const line = printed.split(`
10339
10686
  `).find((one) => one.trim() !== "")?.trim() ?? "";
@@ -10346,12 +10693,12 @@ function readLaunched(printed) {
10346
10693
 
10347
10694
  // src/run-record.ts
10348
10695
  import { createHash as createHash2 } from "node:crypto";
10349
- import { mkdirSync as mkdirSync2, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync8, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
10350
- 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";
10351
10698
 
10352
10699
  // src/runtime-client.ts
10353
10700
  import { tmpdir as tmpdir7 } from "node:os";
10354
- 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";
10355
10702
 
10356
10703
  // src/launch.ts
10357
10704
  function envValue(name, variables = process.env) {
@@ -10396,7 +10743,7 @@ function runtimeDirectory(variables = process.env) {
10396
10743
  return explicit;
10397
10744
  }
10398
10745
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
10399
- return join8(perUser ?? tmpdir7(), "gdharness");
10746
+ return join9(perUser ?? tmpdir7(), "gdharness");
10400
10747
  }
10401
10748
  var ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
10402
10749
  var verdicts = new Map;
@@ -10406,15 +10753,15 @@ var untaken = new Map;
10406
10753
 
10407
10754
  // src/run-record.ts
10408
10755
  function runsDirectory() {
10409
- return join9(runtimeDirectory(), "runs");
10756
+ return join10(runtimeDirectory(), "runs");
10410
10757
  }
10411
10758
  function projectKey(projectPath) {
10412
- const real = realAsFarAsItExists(resolve4(projectPath)).replace(/[\\/]+$/, "");
10759
+ const real = realAsFarAsItExists(resolve5(projectPath)).replace(/[\\/]+$/, "");
10413
10760
  const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
10414
10761
  return createHash2("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
10415
10762
  }
10416
10763
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
10417
- return join9(directory, "runs", `note-${projectKey(projectPath)}.json`);
10764
+ return join10(directory, "runs", `note-${projectKey(projectPath)}.json`);
10418
10765
  }
10419
10766
  function writeRunRecord(record) {
10420
10767
  mkdirSync2(runsDirectory(), { recursive: true });
@@ -10442,11 +10789,11 @@ function recordRunEnded(projectPath, pid, ending) {
10442
10789
  function recordAt(path) {
10443
10790
  let parsed;
10444
10791
  try {
10445
- parsed = JSON.parse(readFileSync8(path, "utf8"));
10792
+ parsed = JSON.parse(readFileSync9(path, "utf8"));
10446
10793
  } catch {
10447
10794
  return null;
10448
10795
  }
10449
- if (!parsed || typeof parsed !== "object") {
10796
+ if (!isRecord(parsed)) {
10450
10797
  return null;
10451
10798
  }
10452
10799
  const fields = parsed;
@@ -10484,11 +10831,10 @@ function recordAt(path) {
10484
10831
  };
10485
10832
  }
10486
10833
  function restartedFromAt(value) {
10487
- if (!value || typeof value !== "object") {
10834
+ if (!isRecord(value)) {
10488
10835
  return [];
10489
10836
  }
10490
- const fields = value;
10491
- const { pid, endedAt, exitCode, exitSignal } = fields;
10837
+ const { pid, endedAt, exitCode, exitSignal } = value;
10492
10838
  if (typeof pid !== "number" || typeof endedAt !== "number") {
10493
10839
  return [];
10494
10840
  }
@@ -10862,9 +11208,9 @@ async function startDirectly(spec, output, hidden, detached = true, errors = out
10862
11208
  }
10863
11209
  async function startThroughHelper(spec, output, stopFile) {
10864
11210
  const scratch = scratchDirectory("desktop");
10865
- const pidFile = join10(scratch, "pid");
10866
- const errorFile = join10(scratch, "error");
10867
- const stepFile = join10(scratch, "step");
11211
+ const pidFile = join11(scratch, "pid");
11212
+ const errorFile = join11(scratch, "error");
11213
+ const stepFile = join11(scratch, "step");
10868
11214
  const where = spec.desktop !== undefined ? `on the ${spec.desktop} desktop` : spec.noActivate === true ? "without activation" : "through the desktop helper";
10869
11215
  try {
10870
11216
  const compiled = await ensureHelperCompiled();
@@ -10872,7 +11218,7 @@ async function startThroughHelper(spec, output, stopFile) {
10872
11218
  return { error: `it could not be started ${where}: ${compiled}` };
10873
11219
  }
10874
11220
  const wrapper = throughHelper({ ...spec, directory: process.cwd() }, spec.command, spec.args, {
10875
- spec: join10(scratch, "spec.json"),
11221
+ spec: join11(scratch, "spec.json"),
10876
11222
  pid: pidFile,
10877
11223
  error: errorFile,
10878
11224
  stop: stopFile,
@@ -10888,7 +11234,7 @@ async function startThroughHelper(spec, output, stopFile) {
10888
11234
  exited.now = true;
10889
11235
  });
10890
11236
  for (let waited = 0;waited < DESKTOP_START_MS; waited += 50) {
10891
- const pid = existsSync3(pidFile) ? Number(readFileSync9(pidFile, "utf8").trim()) : Number.NaN;
11237
+ const pid = existsSync5(pidFile) ? Number(readFileSync10(pidFile, "utf8").trim()) : Number.NaN;
10892
11238
  if (Number.isInteger(pid) && pid > 0) {
10893
11239
  return { child: started.child, pid };
10894
11240
  }
@@ -10919,7 +11265,7 @@ function profiledBy(path, projectPath) {
10919
11265
  };
10920
11266
  let projectGodot = null;
10921
11267
  try {
10922
- projectGodot = readFileSync9(join10(projectPath, "project.godot"), "utf8");
11268
+ projectGodot = readFileSync10(join11(projectPath, "project.godot"), "utf8");
10923
11269
  } catch {}
10924
11270
  const observer = new ProfileObserver((totals) => {
10925
11271
  const first = latest === null;
@@ -10937,9 +11283,13 @@ function profiledBy(path, projectPath) {
10937
11283
  if (timer !== null) {
10938
11284
  clearTimeout(timer);
10939
11285
  }
10940
- latest = observer.totals() ?? latest;
11286
+ const last = observer.totals() ?? latest;
11287
+ latest = last;
11288
+ if (last === null) {
11289
+ return;
11290
+ }
10941
11291
  let tries = 0;
10942
- while (latest !== null && !writeProfileFile(path, { ...latest, connected: true }) && tries < LAST_WRITE_TRIES) {
11292
+ while (!writeProfileFile(path, { ...last, connected: true }) && tries < LAST_WRITE_TRIES) {
10943
11293
  tries += 1;
10944
11294
  }
10945
11295
  }
@@ -11020,12 +11370,12 @@ async function keep() {
11020
11370
  }
11021
11371
  const profiled = run?.profile === undefined ? null : profiledBy(run.profile, run.projectPath);
11022
11372
  const debuggerPeer = run === undefined || run.debugged === false ? null : await GameDebugger.open(run.breakpoints ?? [], profiled === null ? [] : [profiled.observer]);
11023
- const exitNotes = run === undefined ? null : mkdtempSync4(join10(tmpdir8(), "gdharness-exit-"));
11373
+ const exitNotes = run === undefined ? null : mkdtempSync4(join11(tmpdir8(), "gdharness-exit-"));
11024
11374
  const noted = exitNotes === null ? spec : { ...spec, envChanges: { ...spec.envChanges, [EXIT_NOTES_VARIABLE]: exitNotes } };
11025
11375
  const toStart = debuggerPeer === null ? noted : { ...noted, args: withEngineArguments(noted.args, debuggerPeer.engineArguments) };
11026
11376
  const transcript = run === undefined ? "ignore" : openSync4(run.transcript, "a");
11027
- const own = run !== undefined && throughTheHelper(spec) ? mkdtempSync4(join10(tmpdir8(), "gdharness-stop-")) : null;
11028
- 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");
11029
11379
  let started;
11030
11380
  const keeperStarted = run?.slot === undefined ? undefined : ownStart();
11031
11381
  const spawnedAt = Date.now();
@@ -11062,7 +11412,7 @@ async function keep() {
11062
11412
  let listening = null;
11063
11413
  let end = () => {
11064
11414
  if (stopFile !== "") {
11065
- writeFileSync4(stopFile, "");
11415
+ writeFileSync5(stopFile, "");
11066
11416
  return "signalled";
11067
11417
  }
11068
11418
  return child.kill() ? "signalled" : "gone";
@@ -11164,7 +11514,7 @@ async function keep() {
11164
11514
  listening?.close();
11165
11515
  for (const made of [own, exitNotes]) {
11166
11516
  if (made !== null) {
11167
- rmSync7(made, { recursive: true, force: true });
11517
+ rmSync8(made, { recursive: true, force: true });
11168
11518
  }
11169
11519
  }
11170
11520
  process.exit(0);
@@ -11175,7 +11525,7 @@ function restartSaid(notes, pid) {
11175
11525
  return null;
11176
11526
  }
11177
11527
  try {
11178
- 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"));
11179
11529
  return typeof said === "object" && said !== null && "restart" in said && typeof said.restart === "boolean" ? said.restart : null;
11180
11530
  } catch {
11181
11531
  return null;