gdharness 1.3.66 → 1.3.68

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 (25) hide show
  1. package/README.md +1 -1
  2. package/build/cli.js +19209 -17814
  3. package/build/godot/addons/gdharness_editor/bridge_client.gd +18 -5
  4. package/build/godot/addons/gdharness_editor/headless_check.gd +52 -0
  5. package/build/godot/addons/gdharness_editor/picture_saves.gd +48 -0
  6. package/build/godot/addons/gdharness_editor/picture_saves.gd.uid +1 -0
  7. package/build/godot/addons/gdharness_editor/plugin.gd +84 -3
  8. package/build/godot/addons/gdharness_editor/scene_file.gd +133 -12
  9. package/build/godot/addons/gdharness_editor/tool_executor.gd +16 -1
  10. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +3 -1
  11. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +9 -13
  12. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +87 -13
  13. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +20 -4
  14. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +51 -17
  15. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +473 -8
  16. package/build/godot/addons/gdharness_editor/tools/view_tools.gd +121 -0
  17. package/build/godot/addons/gdharness_editor/tools/view_tools.gd.uid +1 -0
  18. package/build/godot/addons/gdharness_runtime/picture_saves.gd +48 -0
  19. package/build/godot/addons/gdharness_runtime/picture_saves.gd.uid +1 -0
  20. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -43
  21. package/build/godot/operations/editor_tools.gd +1 -0
  22. package/build/godot/operations/gdscript_analysis.gd +17 -0
  23. package/build/godot/operations/picture_saves.gd +48 -0
  24. package/build/keeper.js +116 -114
  25. package/package.json +1 -1
@@ -0,0 +1,48 @@
1
+ extends RefCounted
2
+
3
+ # How a captured picture is sized and saved, for the game's captures and the editor's alike.
4
+ #
5
+ # This file is the one beside the operations, and `bun run sync:gd` copies it into each addon,
6
+ # because an addon is installed as a directory and cannot preload out of one.
7
+
8
+ ## Measured on a downstream game's 1280x720 frame: its interface text and pixel art read as they do
9
+ ## in the PNG, at a fifth of the PNG's size.
10
+ const JPEG_QUALITY: float = 0.8
11
+
12
+
13
+ ## The size a capture drawn at [param drawn] is scaled to, from the width and height asked for, 0
14
+ ## being not asked. One side alone keeps the picture's proportions, since that is what asking for a
15
+ ## smaller picture means: it was ignored unless both came, so the full-size picture was sent back.
16
+ static func scaled_to(drawn: Vector2i, width: int, height: int) -> Vector2i:
17
+ if width > 0 and height > 0:
18
+ return Vector2i(width, height)
19
+ if width > 0 and drawn.x > 0:
20
+ return Vector2i(width, maxi(1, roundi(float(drawn.y) * width / drawn.x)))
21
+ if height > 0 and drawn.y > 0:
22
+ return Vector2i(maxi(1, roundi(float(drawn.x) * height / drawn.y)), height)
23
+ return drawn
24
+
25
+
26
+ ## [param image] saved as a PNG at [param png_path], and as a JPEG beside it, which the server sends
27
+ ## instead when it is the smaller: a rendered frame is several times smaller as one, and every
28
+ ## picture sent stays in the caller's session. Not for a picture with transparency, which a JPEG
29
+ ## would flatten.
30
+ static func saved(image: Image, png_path: String) -> Dictionary:
31
+ var save_error: Error = image.save_png(png_path)
32
+ if save_error != OK:
33
+ return {
34
+ "type": "error",
35
+ "message": "Failed to save screenshot as PNG: " + str(save_error),
36
+ }
37
+ var answer: Dictionary = {
38
+ "type": "screenshot_file",
39
+ "format": "png",
40
+ "width": image.get_width(),
41
+ "height": image.get_height(),
42
+ "path": png_path,
43
+ }
44
+ if image.detect_alpha() == Image.ALPHA_NONE:
45
+ var jpeg_path: String = png_path.get_basename() + ".jpg"
46
+ if image.save_jpg(jpeg_path, JPEG_QUALITY) == OK:
47
+ answer["jpegPath"] = jpeg_path
48
+ return answer
@@ -0,0 +1 @@
1
+ uid://c3m5b63kjrfs2
@@ -5,12 +5,10 @@ const GdhRead = preload("reading.gd")
5
5
  const GdhValues = preload("runtime_values.gd")
6
6
  const GdhQueries = preload("runtime_queries.gd")
7
7
  const GdhScreen = preload("runtime_screen.gd")
8
+ const GdhPictures = preload("picture_saves.gd")
8
9
  ## How far zoom enlarges at most. Sixteen makes a 3-pixel ring 48 pixels across, past any need to
9
10
  ## see it, and a 200-pixel region 3200, past the size anybody reads a picture at.
10
11
  const ZOOM_MOST: int = 16
11
- ## Measured on a downstream game's 1280x720 frame: its interface text and pixel art read as they do
12
- ## in the PNG, at a fifth of the PNG's size.
13
- const JPEG_QUALITY: float = 0.8
14
12
 
15
13
  var _host: Node
16
14
  var _values: GdhValues
@@ -78,6 +76,8 @@ func capture_viewport(params: Dictionary) -> Dictionary:
78
76
  # refuses that state itself.
79
77
  if mode == SubViewport.UPDATE_ALWAYS or not _host.get_tree().root.can_draw():
80
78
  return await _capture(viewport, params)
79
+ # Asked for before the wait: in low-processor mode a still game draws no frame of its own, and
80
+ # this request is what makes it draw the one waited for.
81
81
  sub.render_target_update_mode = SubViewport.UPDATE_ONCE
82
82
  await RenderingServer.frame_post_draw
83
83
  var answer: Dictionary = await _capture(viewport, params)
@@ -116,19 +116,6 @@ func _lay_native_windows(image: Image) -> void:
116
116
  )
117
117
 
118
118
 
119
- ## The size a capture drawn at [param drawn] is scaled to, from the width and height asked for, 0
120
- ## being not asked. One side alone keeps the picture's proportions, since that is what asking for a
121
- ## smaller picture means: it was ignored unless both came, so the full-size picture was sent back.
122
- static func scaled_to(drawn: Vector2i, width: int, height: int) -> Vector2i:
123
- if width > 0 and height > 0:
124
- return Vector2i(width, height)
125
- if width > 0 and drawn.x > 0:
126
- return Vector2i(width, maxi(1, roundi(float(drawn.y) * width / drawn.x)))
127
- if height > 0 and drawn.y > 0:
128
- return Vector2i(maxi(1, roundi(float(drawn.x) * height / drawn.y)), height)
129
- return drawn
130
-
131
-
132
119
  ## What part of the picture a capture is cut to and how far it is enlarged, from what was asked, or
133
120
  ## a refusal saying what is wrong with the asking. Read before a frame is waited for, so a call that
134
121
  ## cannot be answered costs no wait.
@@ -390,7 +377,7 @@ func _capture(viewport: Viewport, params: Dictionary, with_windows: bool = false
390
377
  described["zoom"] = zoom
391
378
  else:
392
379
  var drawn: Vector2i = Vector2i(image.get_width(), image.get_height())
393
- var target: Vector2i = scaled_to(
380
+ var target: Vector2i = GdhPictures.scaled_to(
394
381
  drawn, GdhRead.as_int(params.get("width", 0)), GdhRead.as_int(params.get("height", 0))
395
382
  )
396
383
  if target != drawn:
@@ -412,31 +399,6 @@ func _capture(viewport: Viewport, params: Dictionary, with_windows: bool = false
412
399
  % screenshot_path.get_base_dir()
413
400
  )
414
401
  )
415
- var answer: Dictionary = saved(image, screenshot_path)
402
+ var answer: Dictionary = GdhPictures.saved(image, screenshot_path)
416
403
  answer.merge(described)
417
404
  return answer
418
-
419
-
420
- ## [param image] saved as a PNG at [param png_path], and as a JPEG beside it, which the server sends
421
- ## instead when it is the smaller: a rendered frame is several times smaller as one, and every
422
- ## picture sent stays in the caller's session. Not for a picture with transparency, which a JPEG
423
- ## would flatten.
424
- static func saved(image: Image, png_path: String) -> Dictionary:
425
- var save_error: Error = image.save_png(png_path)
426
- if save_error != OK:
427
- return {
428
- "type": "error",
429
- "message": "Failed to save screenshot as PNG: " + str(save_error),
430
- }
431
- var answer: Dictionary = {
432
- "type": "screenshot_file",
433
- "format": "png",
434
- "width": image.get_width(),
435
- "height": image.get_height(),
436
- "path": png_path,
437
- }
438
- if image.detect_alpha() == Image.ALPHA_NONE:
439
- var jpeg_path: String = png_path.get_basename() + ".jpg"
440
- if image.save_jpg(jpeg_path, JPEG_QUALITY) == OK:
441
- answer["jpegPath"] = jpeg_path
442
- return answer
@@ -23,6 +23,7 @@ const SCENE_COMMANDS: Array[String] = [
23
23
  "set_node_properties",
24
24
  "duplicate_node",
25
25
  "reparent_node",
26
+ "save_branch",
26
27
  "delete_node",
27
28
  "bake_navigation",
28
29
  ]
@@ -331,6 +331,23 @@ static func _not_a_script(path: String) -> String:
331
331
  )
332
332
  "tres", "res":
333
333
  return said + ": resource_edit read reads a resource."
334
+ "":
335
+ # A class name given where the path goes.
336
+ var named: String = path.get_file()
337
+ for entry: Dictionary in ProjectSettings.get_global_class_list():
338
+ if str(entry.get("class", "")) == named:
339
+ return (
340
+ said
341
+ + (
342
+ ": %s is the class_name of %s. Call script_info with that path."
343
+ % [named, str(entry.get("path", ""))]
344
+ )
345
+ )
346
+ if ClassDB.class_exists(named):
347
+ return (
348
+ said
349
+ + ": %s is an engine class. Call editor_classes info for its members." % named
350
+ )
334
351
  return said + "."
335
352
 
336
353
 
@@ -0,0 +1,48 @@
1
+ extends RefCounted
2
+
3
+ # How a captured picture is sized and saved, for the game's captures and the editor's alike.
4
+ #
5
+ # This file is the one beside the operations, and `bun run sync:gd` copies it into each addon,
6
+ # because an addon is installed as a directory and cannot preload out of one.
7
+
8
+ ## Measured on a downstream game's 1280x720 frame: its interface text and pixel art read as they do
9
+ ## in the PNG, at a fifth of the PNG's size.
10
+ const JPEG_QUALITY: float = 0.8
11
+
12
+
13
+ ## The size a capture drawn at [param drawn] is scaled to, from the width and height asked for, 0
14
+ ## being not asked. One side alone keeps the picture's proportions, since that is what asking for a
15
+ ## smaller picture means: it was ignored unless both came, so the full-size picture was sent back.
16
+ static func scaled_to(drawn: Vector2i, width: int, height: int) -> Vector2i:
17
+ if width > 0 and height > 0:
18
+ return Vector2i(width, height)
19
+ if width > 0 and drawn.x > 0:
20
+ return Vector2i(width, maxi(1, roundi(float(drawn.y) * width / drawn.x)))
21
+ if height > 0 and drawn.y > 0:
22
+ return Vector2i(maxi(1, roundi(float(drawn.x) * height / drawn.y)), height)
23
+ return drawn
24
+
25
+
26
+ ## [param image] saved as a PNG at [param png_path], and as a JPEG beside it, which the server sends
27
+ ## instead when it is the smaller: a rendered frame is several times smaller as one, and every
28
+ ## picture sent stays in the caller's session. Not for a picture with transparency, which a JPEG
29
+ ## would flatten.
30
+ static func saved(image: Image, png_path: String) -> Dictionary:
31
+ var save_error: Error = image.save_png(png_path)
32
+ if save_error != OK:
33
+ return {
34
+ "type": "error",
35
+ "message": "Failed to save screenshot as PNG: " + str(save_error),
36
+ }
37
+ var answer: Dictionary = {
38
+ "type": "screenshot_file",
39
+ "format": "png",
40
+ "width": image.get_width(),
41
+ "height": image.get_height(),
42
+ "path": png_path,
43
+ }
44
+ if image.detect_alpha() == Image.ALPHA_NONE:
45
+ var jpeg_path: String = png_path.get_basename() + ".jpg"
46
+ if image.save_jpg(jpeg_path, JPEG_QUALITY) == OK:
47
+ answer["jpegPath"] = jpeg_path
48
+ return answer
package/build/keeper.js CHANGED
@@ -7265,14 +7265,16 @@ class CompiledHelper {
7265
7265
  file;
7266
7266
  source;
7267
7267
  digest;
7268
- constructor(named, file, source) {
7268
+ program;
7269
+ constructor(named, file, source, program = false) {
7269
7270
  this.named = named;
7270
7271
  this.file = file;
7271
7272
  this.source = source;
7273
+ this.program = program;
7272
7274
  this.digest = createHash("sha256").update(source).digest("hex").slice(0, 16);
7273
7275
  }
7274
7276
  assembly(directory = tmpdir()) {
7275
- return join2(directory, `${this.file}-${this.digest}.dll`);
7277
+ return join2(directory, `${this.file}-${this.digest}.${this.program ? "exe" : "dll"}`);
7276
7278
  }
7277
7279
  async ensure(directory = tmpdir(), budgetMs = HELPER_COMPILE_MS, wait = true) {
7278
7280
  const assembly = this.assembly(directory);
@@ -7319,8 +7321,8 @@ class CompiledHelper {
7319
7321
  const script = [
7320
7322
  "$ErrorActionPreference = 'Stop'",
7321
7323
  "try {",
7322
- ` $partial = ${literal(assembly)} + '.' + $PID`,
7323
- ` Add-Type -TypeDefinition ([IO.File]::ReadAllText(${literal(source)}, [Text.Encoding]::UTF8)) -OutputAssembly $partial`,
7324
+ ` $partial = ${literal(assembly.slice(0, -4))} + '.' + $PID + ${literal(assembly.slice(-4))}`,
7325
+ ` Add-Type -TypeDefinition ([IO.File]::ReadAllText(${literal(source)}, [Text.Encoding]::UTF8)) -OutputAssembly $partial${this.program ? " -OutputType ConsoleApplication" : ""}`,
7324
7326
  ` Move-Item -LiteralPath $partial -Destination ${literal(assembly)}`,
7325
7327
  "} catch {",
7326
7328
  " [Console]::Error.WriteLine($_.Exception.Message)",
@@ -7466,6 +7468,18 @@ public static class GdharnessDesktop {
7466
7468
  }
7467
7469
  }
7468
7470
 
7471
+ // The spec, the error file and the step file, by path. What stops it before the pid is written
7472
+ // goes to the error file, since its own streams are the ones the process inherits.
7473
+ public static int Main(string[] args) {
7474
+ try {
7475
+ Step(args[2], "reading its spec");
7476
+ return RunSpec(args[0]);
7477
+ } catch (Exception e) {
7478
+ File.WriteAllText(args[1], e.Message);
7479
+ return 1;
7480
+ }
7481
+ }
7482
+
7469
7483
  // Starts the command line, both of its streams on this process's output, writes its pid, and
7470
7484
  // waits for it: the answer is its exit code. On the named desktop when there is one, whose handle
7471
7485
  // is held until then, since a desktop goes when the last handle to it does, and on this process's
@@ -7474,10 +7488,8 @@ public static class GdharnessDesktop {
7474
7488
  // and the foreground is held while it starts, since Godot also asks for it outright. The process
7475
7489
  // is ended through the handle held here once stopFile appears, which is how a keeper ends a game
7476
7490
  // it did not start itself: a handle names one process, where a pid can name the next one too. The
7477
- // process starts in directory rather than in this one's, which is a temporary directory: Windows
7478
- // PowerShell saves its module cache relative to where it runs when the profile does not resolve.
7479
- // The spec is one field a line, each base64 of its UTF-8, read with no PowerShell cmdlet: a cmdlet
7480
- // has PowerShell prepare its modules first, on every start.
7491
+ // process starts in directory rather than in this one's, which is a temporary directory. The spec
7492
+ // is one field a line, each base64 of its UTF-8.
7481
7493
  public static int RunSpec(string specFile) {
7482
7494
  string[] lines = File.ReadAllLines(specFile);
7483
7495
  Func<int, string> field = delegate(int index) { return Encoding.UTF8.GetString(Convert.FromBase64String(lines[index])); };
@@ -7537,8 +7549,8 @@ public static class GdharnessDesktop {
7537
7549
  Started started;
7538
7550
  // Inherited by what it starts: without SEM_NOGPFAULTERRORBOX, Windows Error Reporting runs when
7539
7551
  // the target crashes and writes its dump, and with the other two no hard-error box can open.
7540
- // Set outright rather than cleared from this process's own: Windows PowerShell changes the mode
7541
- // it inherited as it starts, and was seen reading 0x1 here, without the open-file bit.
7552
+ // Set outright rather than cleared from this process's own, which is whatever started it chose:
7553
+ // a Windows PowerShell host was seen reading 0x1 here, without the open-file bit.
7542
7554
  SetErrorMode(FailCriticalErrors | NoOpenFileErrorBox);
7543
7555
  if (noActivate) {
7544
7556
  LockSetForegroundWindow(LockForeground);
@@ -7586,7 +7598,7 @@ public static class GdharnessDesktop {
7586
7598
  }
7587
7599
  }
7588
7600
  `;
7589
- var HELPER = new CompiledHelper("desktop helper", "gdharness-desktop", SOURCE);
7601
+ var HELPER = new CompiledHelper("desktop helper", "gdharness-desktop", SOURCE, true);
7590
7602
  function helperAssembly(directory = tmpdir2()) {
7591
7603
  return HELPER.assembly(directory);
7592
7604
  }
@@ -7625,7 +7637,7 @@ function helperStep(stepFile) {
7625
7637
  try {
7626
7638
  step = readFileSync3(stepFile, "utf8").trim();
7627
7639
  } catch {}
7628
- return step === null || step === "" ? "Windows PowerShell had not begun running the helper" : `the helper was ${step}`;
7640
+ return step === null || step === "" ? "the helper had not begun" : `the helper was ${step}`;
7629
7641
  }
7630
7642
  function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7631
7643
  mendBeforeStart(args, how.directory);
@@ -7641,31 +7653,7 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7641
7653
  ];
7642
7654
  writeFileSync3(files.spec, fields.map((field) => Buffer.from(field, "utf8").toString("base64")).join(`
7643
7655
  `), "utf8");
7644
- const literal = (path) => `'${path.replaceAll("'", "''")}'`;
7645
- const script = [
7646
- "$ErrorActionPreference = 'Stop'",
7647
- "try {",
7648
- ` [IO.File]::WriteAllText(${literal(files.step)}, 'loading its assembly')`,
7649
- ` $null = [Reflection.Assembly]::LoadFrom(${literal(assembly)})`,
7650
- ` $code = [GdharnessDesktop]::RunSpec(${literal(files.spec)})`,
7651
- "} catch {",
7652
- ` [IO.File]::WriteAllText(${literal(files.error)}, $_.Exception.Message)`,
7653
- " exit 1",
7654
- "}",
7655
- "exit $code"
7656
- ].join(`
7657
- `);
7658
- return {
7659
- command: "powershell.exe",
7660
- args: [
7661
- "-NoProfile",
7662
- "-NonInteractive",
7663
- "-ExecutionPolicy",
7664
- "Bypass",
7665
- "-EncodedCommand",
7666
- Buffer.from(script, "utf16le").toString("base64")
7667
- ]
7668
- };
7656
+ return { command: assembly, args: [files.spec, files.error, files.step] };
7669
7657
  }
7670
7658
 
7671
7659
  // src/engine-slots.ts
@@ -9505,6 +9493,97 @@ var WINDOWS_STATUSES = new Map([
9505
9493
  [3221226505, "a stack buffer overrun or a fast fail, so it crashed"]
9506
9494
  ]);
9507
9495
 
9496
+ // src/scratch.ts
9497
+ import { existsSync as existsSync4, mkdtempSync as mkdtempSync2, readdirSync as readdirSync3, rmSync as rmSync5 } from "node:fs";
9498
+ import { tmpdir as tmpdir5 } from "node:os";
9499
+ import { join as join5 } from "node:path";
9500
+ import process6 from "node:process";
9501
+ var SWEPT = { recursive: true, force: true };
9502
+ var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
9503
+ var waiting = new Set;
9504
+ var atExit = new Set;
9505
+ function scratchDirectory(kind) {
9506
+ return mkdtempSync2(join5(tmpdir5(), `gdharness-${kind}-${process6.pid}-`));
9507
+ }
9508
+ function discardWith(removePath, paths) {
9509
+ const left = [];
9510
+ for (const path of paths) {
9511
+ if (!path) {
9512
+ continue;
9513
+ }
9514
+ try {
9515
+ removePath(path);
9516
+ } catch (error) {
9517
+ left.push({ path, reason: error instanceof Error ? error.message : String(error) });
9518
+ }
9519
+ }
9520
+ return left;
9521
+ }
9522
+ function discard(...paths) {
9523
+ const left = discardWith(remove, paths);
9524
+ for (const { path } of left) {
9525
+ tryAgain(path, 0);
9526
+ }
9527
+ return left;
9528
+ }
9529
+ function remove(path) {
9530
+ rmSync5(path, SWEPT);
9531
+ }
9532
+ function tryAgain(path, attempt) {
9533
+ if (attempt === 0) {
9534
+ if (waiting.has(path)) {
9535
+ return;
9536
+ }
9537
+ waiting.add(path);
9538
+ atExit.delete(path);
9539
+ lastTryOnExit();
9540
+ }
9541
+ const after = AGAIN_AFTER_MS[attempt];
9542
+ if (after === undefined || !existsSync4(path)) {
9543
+ waiting.delete(path);
9544
+ if (after === undefined) {
9545
+ atExit.add(path);
9546
+ }
9547
+ return;
9548
+ }
9549
+ setTimeout(() => {
9550
+ try {
9551
+ remove(path);
9552
+ waiting.delete(path);
9553
+ } catch {
9554
+ tryAgain(path, attempt + 1);
9555
+ }
9556
+ }, after).unref();
9557
+ }
9558
+ var lastTryWatched = false;
9559
+ function lastTryOnExit() {
9560
+ if (lastTryWatched) {
9561
+ return;
9562
+ }
9563
+ lastTryWatched = true;
9564
+ process6.once("exit", () => {
9565
+ for (const path of [...waiting, ...atExit]) {
9566
+ try {
9567
+ remove(path);
9568
+ } catch {}
9569
+ }
9570
+ });
9571
+ }
9572
+
9573
+ // src/doc-cache.ts
9574
+ var EMPTY_SCRIPT_DOCS = [
9575
+ "extends SceneTree",
9576
+ "",
9577
+ "",
9578
+ "func _initialize() -> void:",
9579
+ "\tvar cache: Resource = Resource.new()",
9580
+ '\tcache.set_meta("classes", [])',
9581
+ "\tvar saved: Error = ResourceSaver.save(cache, OS.get_cmdline_user_args()[0], ResourceSaver.FLAG_COMPRESS)",
9582
+ "\tquit(0 if saved == OK else 1)",
9583
+ ""
9584
+ ].join(`
9585
+ `);
9586
+
9508
9587
  // src/project-scan.ts
9509
9588
  var SKIPPED = new Set(["node_modules"]);
9510
9589
  var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
@@ -9591,83 +9670,6 @@ var IMPORTABLE_EXTENSIONS = [
9591
9670
  var IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
9592
9671
  var hashes = new Map;
9593
9672
 
9594
- // src/scratch.ts
9595
- import { existsSync as existsSync4, mkdtempSync as mkdtempSync2, readdirSync as readdirSync3, rmSync as rmSync5 } from "node:fs";
9596
- import { tmpdir as tmpdir5 } from "node:os";
9597
- import { join as join5 } from "node:path";
9598
- import process6 from "node:process";
9599
- var SWEPT = { recursive: true, force: true };
9600
- var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
9601
- var waiting = new Set;
9602
- var atExit = new Set;
9603
- function scratchDirectory(kind) {
9604
- return mkdtempSync2(join5(tmpdir5(), `gdharness-${kind}-${process6.pid}-`));
9605
- }
9606
- function discardWith(removePath, paths) {
9607
- const left = [];
9608
- for (const path of paths) {
9609
- if (!path) {
9610
- continue;
9611
- }
9612
- try {
9613
- removePath(path);
9614
- } catch (error) {
9615
- left.push({ path, reason: error instanceof Error ? error.message : String(error) });
9616
- }
9617
- }
9618
- return left;
9619
- }
9620
- function discard(...paths) {
9621
- const left = discardWith(remove, paths);
9622
- for (const { path } of left) {
9623
- tryAgain(path, 0);
9624
- }
9625
- return left;
9626
- }
9627
- function remove(path) {
9628
- rmSync5(path, SWEPT);
9629
- }
9630
- function tryAgain(path, attempt) {
9631
- if (attempt === 0) {
9632
- if (waiting.has(path)) {
9633
- return;
9634
- }
9635
- waiting.add(path);
9636
- atExit.delete(path);
9637
- lastTryOnExit();
9638
- }
9639
- const after = AGAIN_AFTER_MS[attempt];
9640
- if (after === undefined || !existsSync4(path)) {
9641
- waiting.delete(path);
9642
- if (after === undefined) {
9643
- atExit.add(path);
9644
- }
9645
- return;
9646
- }
9647
- setTimeout(() => {
9648
- try {
9649
- remove(path);
9650
- waiting.delete(path);
9651
- } catch {
9652
- tryAgain(path, attempt + 1);
9653
- }
9654
- }, after).unref();
9655
- }
9656
- var lastTryWatched = false;
9657
- function lastTryOnExit() {
9658
- if (lastTryWatched) {
9659
- return;
9660
- }
9661
- lastTryWatched = true;
9662
- process6.once("exit", () => {
9663
- for (const path of [...waiting, ...atExit]) {
9664
- try {
9665
- remove(path);
9666
- } catch {}
9667
- }
9668
- });
9669
- }
9670
-
9671
9673
  // src/answer-json.ts
9672
9674
  var SCALAR = String.raw`(?:-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)`;
9673
9675
  var SCALAR_ARRAY = new RegExp(String.raw`\[\n\s+(${SCALAR}(?:,\n\s+${SCALAR})*)\n\s*\]`, "g");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.3.66",
3
+ "version": "1.3.68",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",