gdharness 1.1.1 → 1.1.2
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 +1 -1
- package/build/godot/operations/import_pipeline.gd +102 -8
- package/build/index.js +1 -1
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -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",
|
|
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",
|
|
15996
15996
|
requires: []
|
|
15997
15997
|
},
|
|
15998
15998
|
options: { summary: "the import options of one resource", requires: ["resourcePath"] },
|
|
@@ -380,12 +380,106 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
|
|
|
380
380
|
"source_exists": true
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
-
var
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
"path": resource_path,
|
|
388
|
-
"status": "needs_reimport" if source_modified > import_modified else "up_to_date",
|
|
389
|
-
"import_file_exists": true,
|
|
390
|
-
"source_exists": true
|
|
383
|
+
var status: Dictionary = {
|
|
384
|
+
"path": resource_path, "status": "up_to_date", "import_file_exists": true, "source_exists": true
|
|
391
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
|
+
|
|
391
|
+
# The sidecar alone does not say the import is there: deleting the outputs under
|
|
392
|
+
# .godot/imported is the ordinary way to force one, and a resource whose output is gone was
|
|
393
|
+
# answered as current, which is what a reimport is then skipped on.
|
|
394
|
+
var outputs: Array[String] = _outputs_of(import_file_path)
|
|
395
|
+
var missing: Array[String] = []
|
|
396
|
+
for output: String in outputs:
|
|
397
|
+
if not FileAccess.file_exists(output):
|
|
398
|
+
missing.append(output)
|
|
399
|
+
if not missing.is_empty():
|
|
400
|
+
status["status"] = "needs_reimport"
|
|
401
|
+
status["reason"] = "its imported output is not on disk"
|
|
402
|
+
status["missing_outputs"] = missing
|
|
403
|
+
return status
|
|
404
|
+
|
|
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():
|
|
416
|
+
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
|
|
419
|
+
return status
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
## The files an import wrote, from its sidecar's [code]dest_files[/code]; empty when it lists none.
|
|
423
|
+
static func _outputs_of(import_file_path: String) -> Array[String]:
|
|
424
|
+
var config: ConfigFile = ConfigFile.new()
|
|
425
|
+
var outputs: Array[String] = []
|
|
426
|
+
if config.load(import_file_path) != OK:
|
|
427
|
+
return outputs
|
|
428
|
+
var listed: Variant = config.get_value("deps", "dest_files", [])
|
|
429
|
+
if listed is Array:
|
|
430
|
+
for one: Variant in listed:
|
|
431
|
+
outputs.append(str(one))
|
|
432
|
+
return outputs
|
|
433
|
+
|
|
434
|
+
|
|
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
|
+
## The image files a glTF scene refers to by path, resolved against the scene's own directory.
|
|
455
|
+
## Embedded images and every other scene format answer nothing.
|
|
456
|
+
static func _images_of(scene_path: String) -> Array[String]:
|
|
457
|
+
var images: Array[String] = []
|
|
458
|
+
var text: String = ""
|
|
459
|
+
match scene_path.get_extension().to_lower():
|
|
460
|
+
"gltf":
|
|
461
|
+
text = FileAccess.get_file_as_string(scene_path)
|
|
462
|
+
"glb":
|
|
463
|
+
# A 12-byte header, then the first chunk: its length, its type, and for "JSON" the text.
|
|
464
|
+
var bytes: PackedByteArray = FileAccess.get_file_as_bytes(scene_path)
|
|
465
|
+
if bytes.size() >= 20 and bytes.decode_u32(16) == 0x4E4F534A:
|
|
466
|
+
var length: int = bytes.decode_u32(12)
|
|
467
|
+
text = bytes.slice(20, mini(20 + length, bytes.size())).get_string_from_utf8()
|
|
468
|
+
if text.is_empty():
|
|
469
|
+
return images
|
|
470
|
+
var parsed: Variant = JSON.parse_string(text)
|
|
471
|
+
if not parsed is Dictionary:
|
|
472
|
+
return images
|
|
473
|
+
var document: Dictionary = parsed
|
|
474
|
+
var listed: Variant = document.get("images", [])
|
|
475
|
+
if not listed is Array:
|
|
476
|
+
return images
|
|
477
|
+
for image: Variant in listed:
|
|
478
|
+
if not image is Dictionary:
|
|
479
|
+
continue
|
|
480
|
+
var entry: Dictionary = image
|
|
481
|
+
var uri: String = str(entry.get("uri", ""))
|
|
482
|
+
if uri.is_empty() or uri.begins_with("data:"):
|
|
483
|
+
continue
|
|
484
|
+
images.append(scene_path.get_base_dir().path_join(uri.uri_decode()).simplify_path())
|
|
485
|
+
return images
|
package/build/index.js
CHANGED
|
@@ -30118,7 +30118,7 @@ var TOOL_SPECS = [
|
|
|
30118
30118
|
requires: ["projectPath"],
|
|
30119
30119
|
operations: {
|
|
30120
30120
|
status: {
|
|
30121
|
-
summary: "which resources are outdated or failed, or one resource with resourcePath",
|
|
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
30122
|
requires: []
|
|
30123
30123
|
},
|
|
30124
30124
|
options: { summary: "the import options of one resource", requires: ["resourcePath"] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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",
|