gdharness 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js CHANGED
@@ -114,11 +114,11 @@ function startTimesOf(pids) {
114
114
  }
115
115
  async function processTree() {
116
116
  try {
117
- if (process.platform === "win32") {
118
- return parseProcessTable(await askWindows(), true);
119
- }
120
- return parseProcessTable(await askPosix());
121
- } catch {
117
+ const tree = process.platform === "win32" ? parseProcessTable(await askWindows(), true) : parseProcessTable(await askPosix());
118
+ lastListingFailure = undefined;
119
+ return tree;
120
+ } catch (error) {
121
+ lastListingFailure = error instanceof Error ? error.message : String(error);
122
122
  return;
123
123
  }
124
124
  }
@@ -256,7 +256,7 @@ async function askWindows() {
256
256
  "-NoProfile",
257
257
  "-NonInteractive",
258
258
  "-Command",
259
- `${POWERSHELL_UTF8}Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
259
+ `${POWERSHELL_UTF8}Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate,CommandLine | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
260
260
  ], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
261
261
  return stdout;
262
262
  }
@@ -267,7 +267,7 @@ async function askPosix() {
267
267
  });
268
268
  return stdout;
269
269
  }
270
- var run, POWERSHELL_UTF8 = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ", ASK_TIMEOUT_MS = 15000;
270
+ var run, POWERSHELL_UTF8 = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ", ASK_TIMEOUT_MS = 15000, lastListingFailure;
271
271
  var init_process_children = __esm(() => {
272
272
  run = promisify(execFile);
273
273
  });
@@ -15992,7 +15992,7 @@ var init_tool_definitions = __esm(() => {
15992
15992
  requires: ["projectPath"],
15993
15993
  operations: {
15994
15994
  status: {
15995
- summary: "which resources are outdated or failed, or one resource with resourcePath: a source changed since its import, an import whose output under .godot/imported is gone (missing_outputs), and a glTF scene imported before the images it uses (imported_before), each with its reason",
15995
+ summary: "which resources are outdated or failed, or one resource with resourcePath, judged by content as the editor judges it: a source whose hash differs from the one its import recorded, an import whose output under .godot/imported is gone (missing_outputs), and a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without), each with its reason",
15996
15996
  requires: []
15997
15997
  },
15998
15998
  options: { summary: "the import options of one resource", requires: ["resourcePath"] },
@@ -41334,7 +41334,10 @@ class GodotServer {
41334
41334
  adopted.log.finish();
41335
41335
  adopted.exitCode = record.exitCode ?? null;
41336
41336
  adopted.exitSignal = record.exitSignal ?? null;
41337
- adopted.endedUnwatched = !exited(adopted);
41337
+ const exitMayBeComing = !alive(record.pid) && !exited(adopted);
41338
+ if (!exitMayBeComing) {
41339
+ adopted.endedUnwatched = !exited(adopted);
41340
+ }
41338
41341
  }
41339
41342
  this.activeProcess = adopted;
41340
41343
  return adopted;
@@ -383,10 +383,6 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
383
383
  var status: Dictionary = {
384
384
  "path": resource_path, "status": "up_to_date", "import_file_exists": true, "source_exists": true
385
385
  }
386
- if FileAccess.get_modified_time(resource_path) > FileAccess.get_modified_time(import_file_path):
387
- status["status"] = "needs_reimport"
388
- status["reason"] = "the source changed after it was imported"
389
- return status
390
386
 
391
387
  # The sidecar alone does not say the import is there: deleting the outputs under
392
388
  # .godot/imported is the ordinary way to force one, and a resource whose output is gone was
@@ -402,23 +398,59 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
402
398
  status["missing_outputs"] = missing
403
399
  return status
404
400
 
405
- # A scene imported before the textures it uses were: it loads each image as it imports, so one
406
- # imported ahead of them is built without them, and nothing about its own files says so. A second
407
- # reimport started over one in progress did exactly that downstream, and 64 scenes that were
408
- # untextured all read as up to date here.
409
- var built_at: int = _oldest(outputs, import_file_path)
410
- var before: Array[String] = []
411
- for image: String in _images_of(resource_path):
412
- if FileAccess.file_exists(image + ".import"):
413
- if _newest(_outputs_of(image + ".import"), image + ".import") > built_at:
414
- before.append(image)
415
- if not before.is_empty():
401
+ # By content, as the editor judges it: the hash of the source the import recorded, against the
402
+ # file. By time, an installer that rewrote 31 images byte for byte made every one read as changed,
403
+ # and the editor, which compares the hash, rightly imported none of them.
404
+ var recorded: String = _recorded_source_md5(resource_path)
405
+ var changed: bool = (
406
+ FileAccess.get_md5(resource_path) != recorded
407
+ if not recorded.is_empty()
408
+ else FileAccess.get_modified_time(resource_path) > FileAccess.get_modified_time(import_file_path)
409
+ )
410
+ if changed:
411
+ status["status"] = "needs_reimport"
412
+ status["reason"] = "the source changed after it was imported"
413
+ return status
414
+
415
+ # A scene built without an image it names: it loads each one as it imports, so one imported
416
+ # before the image was is built without it. Read off what the imported scene depends on, not off
417
+ # file times: a texture a 3D scene uses is reimported compressed after the scene, so its outputs
418
+ # are always the newer, and 33 correct scenes read as stale when judged that way.
419
+ var without: Array[String] = []
420
+ var images: Array[String] = _images_of(resource_path)
421
+ if not images.is_empty():
422
+ var depended: PackedStringArray = ResourceLoader.get_dependencies(resource_path)
423
+ for image: String in images:
424
+ if FileAccess.file_exists(image + ".import") and not _names(depended, image):
425
+ without.append(image)
426
+ if not without.is_empty():
416
427
  status["status"] = "needs_reimport"
417
- status["reason"] = "it was imported before the images it uses, so it was built without them"
418
- status["imported_before"] = before
428
+ status["reason"] = "it was imported without images it uses, which were not imported yet"
429
+ status["imported_without"] = without
419
430
  return status
420
431
 
421
432
 
433
+ ## The hash of its source that [param resource_path]'s last import recorded, or "" when there is no
434
+ ## record: the editor keeps it beside the outputs, under the same base name with [code].md5[/code].
435
+ static func _recorded_source_md5(resource_path: String) -> String:
436
+ var record: String = (
437
+ "res://.godot/imported/%s-%s.md5" % [resource_path.get_file(), resource_path.md5_text()]
438
+ )
439
+ var config: ConfigFile = ConfigFile.new()
440
+ if config.load(record) != OK:
441
+ return ""
442
+ return str(config.get_value("", "source_md5", ""))
443
+
444
+
445
+ ## Whether [param dependencies], as [method ResourceLoader.get_dependencies] lists them, include
446
+ ## [param path]; each entry may carry a uid and a type ahead of the path.
447
+ static func _names(dependencies: PackedStringArray, path: String) -> bool:
448
+ for entry: String in dependencies:
449
+ if entry == path or entry.ends_with("::" + path):
450
+ return true
451
+ return false
452
+
453
+
422
454
  ## The files an import wrote, from its sidecar's [code]dest_files[/code]; empty when it lists none.
423
455
  static func _outputs_of(import_file_path: String) -> Array[String]:
424
456
  var config: ConfigFile = ConfigFile.new()
@@ -432,25 +464,6 @@ static func _outputs_of(import_file_path: String) -> Array[String]:
432
464
  return outputs
433
465
 
434
466
 
435
- ## When the earliest of [param outputs] was written, or [param fallback] when none is on disk.
436
- static func _oldest(outputs: Array[String], fallback: String) -> int:
437
- var oldest: int = -1
438
- for output: String in outputs:
439
- if FileAccess.file_exists(output):
440
- var at: int = FileAccess.get_modified_time(output)
441
- oldest = at if oldest < 0 else mini(oldest, at)
442
- return oldest if oldest >= 0 else FileAccess.get_modified_time(fallback)
443
-
444
-
445
- ## When the latest of [param outputs] was written, or [param fallback] when none is on disk.
446
- static func _newest(outputs: Array[String], fallback: String) -> int:
447
- var newest: int = -1
448
- for output: String in outputs:
449
- if FileAccess.file_exists(output):
450
- newest = maxi(newest, FileAccess.get_modified_time(output))
451
- return newest if newest >= 0 else FileAccess.get_modified_time(fallback)
452
-
453
-
454
467
  ## The image files a glTF scene refers to by path, resolved against the scene's own directory.
455
468
  ## Embedded images and every other scene format answer nothing.
456
469
  static func _images_of(scene_path: String) -> Array[String]:
package/build/index.js CHANGED
@@ -24952,14 +24952,15 @@ function startTimesOf(pids) {
24952
24952
  var ASK_TIMEOUT_MS = 15000;
24953
24953
  async function processTree() {
24954
24954
  try {
24955
- if (process.platform === "win32") {
24956
- return parseProcessTable(await askWindows(), true);
24957
- }
24958
- return parseProcessTable(await askPosix());
24959
- } catch {
24955
+ const tree = process.platform === "win32" ? parseProcessTable(await askWindows(), true) : parseProcessTable(await askPosix());
24956
+ lastListingFailure = undefined;
24957
+ return tree;
24958
+ } catch (error) {
24959
+ lastListingFailure = error instanceof Error ? error.message : String(error);
24960
24960
  return;
24961
24961
  }
24962
24962
  }
24963
+ var lastListingFailure;
24963
24964
  function parseProcessTable(printed, started = false) {
24964
24965
  const tree = new Map;
24965
24966
  for (const line of printed.split(/\r?\n/)) {
@@ -25094,7 +25095,7 @@ async function askWindows() {
25094
25095
  "-NoProfile",
25095
25096
  "-NonInteractive",
25096
25097
  "-Command",
25097
- `${POWERSHELL_UTF8}Get-CimInstance Win32_Process | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
25098
+ `${POWERSHELL_UTF8}Get-CimInstance Win32_Process -Property ProcessId,ParentProcessId,CreationDate,CommandLine | ForEach-Object { $began = 0; if ($_.CreationDate) { $began = ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() }; "$($_.ProcessId) $($_.ParentProcessId) $began $($_.CommandLine)" }`
25098
25099
  ], { timeout: ASK_TIMEOUT_MS, windowsHide: true, maxBuffer: 16 * 1024 * 1024 });
25099
25100
  return stdout;
25100
25101
  }
@@ -30118,7 +30119,7 @@ var TOOL_SPECS = [
30118
30119
  requires: ["projectPath"],
30119
30120
  operations: {
30120
30121
  status: {
30121
- summary: "which resources are outdated or failed, or one resource with resourcePath: a source changed since its import, an import whose output under .godot/imported is gone (missing_outputs), and a glTF scene imported before the images it uses (imported_before), each with its reason",
30122
+ summary: "which resources are outdated or failed, or one resource with resourcePath, judged by content as the editor judges it: a source whose hash differs from the one its import recorded, an import whose output under .godot/imported is gone (missing_outputs), and a glTF scene whose import does not depend on an image it names, because it was built before that image was imported (imported_without), each with its reason",
30122
30123
  requires: []
30123
30124
  },
30124
30125
  options: { summary: "the import options of one resource", requires: ["resourcePath"] },
@@ -34072,7 +34073,10 @@ class GodotServer {
34072
34073
  adopted.log.finish();
34073
34074
  adopted.exitCode = record.exitCode ?? null;
34074
34075
  adopted.exitSignal = record.exitSignal ?? null;
34075
- adopted.endedUnwatched = !exited(adopted);
34076
+ const exitMayBeComing = !alive(record.pid) && !exited(adopted);
34077
+ if (!exitMayBeComing) {
34078
+ adopted.endedUnwatched = !exited(adopted);
34079
+ }
34076
34080
  }
34077
34081
  this.activeProcess = adopted;
34078
34082
  return adopted;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
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",