gdharness 1.3.53 → 1.3.54

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
@@ -14312,16 +14312,76 @@ function importedIntoTheProject(projectPath, resPaths) {
14312
14312
  function onDisk(projectPath, resPath) {
14313
14313
  return join13(projectPath, resPath.slice("res://".length));
14314
14314
  }
14315
- function behind(projectPath, resPath, sidecar) {
14315
+ function importedSidecarMd5s(projectPath) {
14316
+ const found = new Map;
14317
+ const editor = join13(projectPath, ".godot", "editor");
14318
+ let newest = -1;
14319
+ try {
14320
+ for (const name of readdirSync8(editor)) {
14321
+ const version = /^filesystem_cache(\d+)$/.exec(name)?.[1];
14322
+ if (version !== undefined) {
14323
+ newest = Math.max(newest, Number(version));
14324
+ }
14325
+ }
14326
+ } catch {
14327
+ return found;
14328
+ }
14329
+ if (newest < 0) {
14330
+ return found;
14331
+ }
14332
+ let text;
14333
+ try {
14334
+ text = readFileSync11(join13(editor, `filesystem_cache${newest}`), "utf8");
14335
+ } catch {
14336
+ return found;
14337
+ }
14338
+ let directory = "res://";
14339
+ for (const line of text.split(/\r?\n/).slice(1).map((one) => one.trim())) {
14340
+ if (line === "") {
14341
+ continue;
14342
+ }
14343
+ const fields = line.split("::");
14344
+ if (line.startsWith("::")) {
14345
+ if (fields.length === 3) {
14346
+ directory = fields[1] ?? directory;
14347
+ }
14348
+ continue;
14349
+ }
14350
+ const slices = fields.length < 9 ? [] : (fields[7] ?? "").split("<>");
14351
+ const md5 = slices[5];
14352
+ if (slices.length >= 7 && md5 !== undefined && md5 !== "") {
14353
+ found.set(`${directory}${directory.endsWith("/") ? "" : "/"}${fields[0]}`, md5);
14354
+ }
14355
+ }
14356
+ return found;
14357
+ }
14358
+ function behind(projectPath, resPath, sidecarFile, sidecar, importedFrom) {
14359
+ const imported = importedFrom.get(resPath);
14360
+ if (imported !== undefined && md5Of(sidecarFile) !== imported) {
14361
+ return "its import file changed after it was imported, as setting an option does";
14362
+ }
14316
14363
  const remap = sectionOf(sidecar, "remap");
14317
14364
  const importer = unquoted(remap.get("importer"));
14318
14365
  if (importer === "keep" || importer === "skip") {
14319
14366
  return null;
14320
14367
  }
14321
- const outputs = listed2(sectionOf(sidecar, "deps").get("dest_files"));
14368
+ const deps = sectionOf(sidecar, "deps");
14369
+ const outputs = listed2(deps.get("dest_files"));
14370
+ if (remap.get("valid") !== "false") {
14371
+ if (!remap.has("uid")) {
14372
+ return "its import file has no uid, which the import writes";
14373
+ }
14374
+ if (outputs.length === 0 && ![...remap.keys()].some((key) => key.startsWith("path"))) {
14375
+ return "its import file names no imported output, as one written before the engine imports from it does";
14376
+ }
14377
+ }
14322
14378
  if (outputs.some((output) => !existsSync11(onDisk(projectPath, output)))) {
14323
14379
  return "its imported output is not on disk";
14324
14380
  }
14381
+ const source = unquoted(deps.get("source_file"));
14382
+ if (source !== "" && source !== resPath) {
14383
+ return `its import file was written for ${source}`;
14384
+ }
14325
14385
  const name = resPath.slice(resPath.lastIndexOf("/") + 1);
14326
14386
  const record = join13(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
14327
14387
  let recorded = "";
@@ -14338,6 +14398,7 @@ function behind(projectPath, resPath, sidecar) {
14338
14398
  }
14339
14399
  function staleImports(projectPath) {
14340
14400
  const stale = [];
14401
+ const importedFrom = importedSidecarMd5s(projectPath);
14341
14402
  const visit = (directory, resDirectory) => {
14342
14403
  if (existsSync11(join13(directory, ".gdignore")) || existsSync11(join13(directory, VERSION_MARKER))) {
14343
14404
  return;
@@ -14366,13 +14427,14 @@ function staleImports(projectPath) {
14366
14427
  if (!names.has(entry.name.slice(0, -".import".length))) {
14367
14428
  continue;
14368
14429
  }
14430
+ const sidecarFile = join13(directory, entry.name);
14369
14431
  let sidecar;
14370
14432
  try {
14371
- sidecar = readFileSync11(join13(directory, entry.name), "utf8");
14433
+ sidecar = readFileSync11(sidecarFile, "utf8");
14372
14434
  } catch {
14373
14435
  continue;
14374
14436
  }
14375
- const reason = behind(projectPath, source, sidecar);
14437
+ const reason = behind(projectPath, source, sidecarFile, sidecar, importedFrom);
14376
14438
  if (reason !== null) {
14377
14439
  stale.push({ path: source, reason });
14378
14440
  }
@@ -60197,6 +60259,7 @@ init_import_freshness();
60197
60259
  init_launch();
60198
60260
  init_library_copies();
60199
60261
  init_log_read();
60262
+ init_reimport();
60200
60263
  init_setup();
60201
60264
  import {
60202
60265
  copyFileSync,
@@ -61146,7 +61209,8 @@ async function importProject(launch) {
61146
61209
  console.log(`Wrote .godot/extension_list.cfg naming ${named(seeded)}, so the import loads them.`);
61147
61210
  }
61148
61211
  const cached = cachedClasses(launch.projectPath);
61149
- if ((seeded === null || seeded.length === 0) && cached !== null && staleAgainst(cached, launch.projectPath).length === 0 && staleImports(launch.projectPath).length === 0) {
61212
+ const stale = staleImports(launch.projectPath);
61213
+ if ((seeded === null || seeded.length === 0) && cached !== null && staleAgainst(cached, launch.projectPath).length === 0 && stale.length === 0) {
61150
61214
  console.log("The import was skipped: every imported file and the class cache are current.");
61151
61215
  return null;
61152
61216
  }
@@ -61156,6 +61220,9 @@ async function importProject(launch) {
61156
61220
  const lock = await launch.slots.take();
61157
61221
  try {
61158
61222
  freeLibraryCopies(launch.projectPath);
61223
+ for (const one of stale) {
61224
+ forceNextImport(launch.projectPath, one.path);
61225
+ }
61159
61226
  const ended = await watchedEngine(launch, lock, ["--headless", "--audio-driver", "Dummy", "--path", launch.projectPath, "--import"], directory, log, launch.silenceFloorSeconds);
61160
61227
  if (ended.ended !== null || ended.code !== 0) {
61161
61228
  return `${ended.ended ?? `the engine exited ${exitNamed(ended.code ?? 0)}`}; everything it printed is in ${launch.logs.named(log)}`;
@@ -574,10 +574,21 @@ static func _import_staleness(resource_path: String, sidecar: ConfigFile) -> Dic
574
574
  if not sidecar.has_section_key("remap", "uid"):
575
575
  return _stale("its import file has no uid, which the import writes")
576
576
 
577
+ # A tool that writes a sidecar for the engine to import from leaves out what the import writes,
578
+ # and every load of the resource then fails. The engine's scan without its cache takes such a
579
+ # sidecar as imported while the record of the last import still matches the source.
580
+ var outputs: Array[String] = _outputs_of(sidecar)
581
+ if outputs.is_empty() and not _names_a_path(sidecar):
582
+ return _stale(
583
+ (
584
+ "its import file names no imported output, as one written before the engine "
585
+ + "imports from it does"
586
+ )
587
+ )
588
+
577
589
  # The sidecar alone does not say the import is there: deleting the outputs under
578
590
  # .godot/imported is the ordinary way to force one, and a resource whose output is gone was
579
591
  # answered as current, which is what a reimport is then skipped on.
580
- var outputs: Array[String] = _outputs_of(sidecar)
581
592
  var missing: Array[String] = []
582
593
  for output: String in outputs:
583
594
  if not FileAccess.file_exists(output):
@@ -786,6 +797,17 @@ static func _outputs_of(sidecar: ConfigFile) -> Array[String]:
786
797
  return outputs
787
798
 
788
799
 
800
+ ## Whether [param sidecar]'s remap names where a load finds the import, as [code]path[/code] or a
801
+ ## [code]path.<feature>[/code] key.
802
+ static func _names_a_path(sidecar: ConfigFile) -> bool:
803
+ if not sidecar.has_section("remap"):
804
+ return false
805
+ for key: String in sidecar.get_section_keys("remap"):
806
+ if key.begins_with("path"):
807
+ return true
808
+ return false
809
+
810
+
789
811
  ## The image files a glTF scene refers to by path, resolved against the scene's own directory.
790
812
  ## Embedded images and every other scene format answer nothing.
791
813
  static func _images_of(scene_path: String) -> Array[String]:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.3.53",
3
+ "version": "1.3.54",
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",