gdharness 1.1.19 → 1.1.21

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.
@@ -734,12 +734,17 @@ func _reread_changed_uids(filesystem: EditorFileSystem) -> Dictionary:
734
734
  return {"reread": reread, "duplicated": duplicated}
735
735
 
736
736
 
737
- ## Every `.uid` under [param directory], skipping what the editor skips: hidden directories and any
738
- ## holding a `.gdignore`.
737
+ ## Every `.uid` under [param directory], skipping what the editor skips: hidden directories, any
738
+ ## holding a `.gdignore`, and any below the project's own that holds a `project.godot` of its own,
739
+ ## which the editor passes over as another project. A nested project's UIDs are never in the editor's
740
+ ## table, so without that last one its files would be read again on every rescan, and one holding a
741
+ ## copy of an outer file's `.uid` named as a duplicate on disk.
739
742
  func _uid_files(directory: String, found: Array[String]) -> void:
740
743
  var listing: DirAccess = DirAccess.open(directory)
741
744
  if listing == null or FileAccess.file_exists(directory.path_join(".gdignore")):
742
745
  return
746
+ if directory != "res://" and FileAccess.file_exists(directory.path_join("project.godot")):
747
+ return
743
748
  for child: String in listing.get_directories():
744
749
  if not child.begins_with("."):
745
750
  _uid_files(directory.path_join(child), found)
@@ -684,9 +684,11 @@ func set_property(params: Dictionary) -> Dictionary:
684
684
  ## A float arrives as 64 bits and most engine properties keep 32: Camera3D.h_offset given -1.1
685
685
  ## reads back as -1.10000002384186, so comparing the two exactly took a write of the value already
686
686
  ## held for one the engine refused (#780). Rounding both to 32 bits is exact for a 32-bit property.
687
- ## A 64-bit one can take a write this calls the same, and then reads back changed, which the caller
688
- ## checks before calling the write unchanged. Vectors and colours hold 32-bit parts in the value
689
- ## itself, so they already compare at the property's precision.
687
+ ## A 64-bit one can take a write this calls the same and read back changed, and the caller calls a
688
+ ## write unchanged only when it reads back as it was. What that cannot tell apart is a 64-bit
689
+ ## property whose setter ignored a value differing from the held one below 32 bits: it is answered
690
+ ## unchanged rather than refused. Vectors and colours hold 32-bit parts in the value itself, so they
691
+ ## already compare at the property's precision.
690
692
  static func _held_already(given: Variant, held: Variant) -> bool:
691
693
  if not Values.comparable(given, held):
692
694
  return false
@@ -145,7 +145,11 @@ func wait_until(params: Dictionary) -> Dictionary:
145
145
  # with null until the time ran out. #772 waited a minute for "null" that way and answered met
146
146
  # false with value null beside it.
147
147
  var declared: int = watched["declared"]
148
- if declared == TYPE_OBJECT and wanted is String and str(wanted).strip_edges() == "null":
148
+ # An untyped slot holding null or an object is read the same way, as set reads it: otherwise the
149
+ # two ops would disagree about one property, and the wait would run out on the text null.
150
+ var untyped_object: bool = declared == TYPE_NIL and (current == null or typeof(current) == TYPE_OBJECT)
151
+ var holds_objects: bool = declared == TYPE_OBJECT or untyped_object
152
+ if holds_objects and wanted is String and str(wanted).strip_edges() == "null":
149
153
  wanted = null
150
154
  var refused: String = _not_comparable(current, wanted, node_path, property)
151
155
  var never_held: bool = typeof(wanted) != TYPE_NIL and typeof(wanted) != TYPE_OBJECT
package/build/index.js CHANGED
@@ -26153,32 +26153,33 @@ function missingMemberIn(message) {
26153
26153
  if (called !== null) {
26154
26154
  return { kind: "static method", member: called[1] ?? "", type: called[2] ?? "" };
26155
26155
  }
26156
- const inBase = /Cannot find member "([A-Za-z_]\w*)" in base "([A-Za-z_]\w*)((?:\.[A-Za-z_]\w*)*)"/.exec(message);
26156
+ const inBase = /Cannot find member "([A-Za-z_]\w*)" in base "([A-Za-z_]\w*)(?:\.([A-Za-z_]\w*))?"/.exec(message);
26157
26157
  if (inBase === null) {
26158
26158
  return null;
26159
26159
  }
26160
26160
  const member = inBase[1] ?? "";
26161
26161
  const type = inBase[2] ?? "";
26162
- const within = (inBase[3] ?? "").split(".").filter((part) => part !== "");
26163
- const enumName = within.at(-1);
26162
+ const enumName = inBase[3];
26164
26163
  return enumName === undefined ? { kind: "member", member, type } : { kind: "enum member", member, type, enum: enumName };
26165
26164
  }
26166
26165
  function declaresMember(source, missing) {
26167
26166
  const name = missing.member.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26167
+ const top = String.raw`^(?:@\w+(?:\([^)]*\))?\s+)*`;
26168
+ const declared = (kinds) => new RegExp(String.raw`${top}(?:${kinds})\s+${name}\b`, "m").test(source);
26168
26169
  if (missing.kind === "method") {
26169
- return new RegExp(String.raw`^\s*(?:static\s+)?func\s+${name}\s*\(`, "m").test(source);
26170
+ return new RegExp(String.raw`${top}(?:static\s+)?func\s+${name}\s*\(`, "m").test(source);
26170
26171
  }
26171
26172
  if (missing.kind === "static method") {
26172
- return new RegExp(String.raw`^\s*static\s+func\s+${name}\s*\(`, "m").test(source);
26173
+ return new RegExp(String.raw`${top}static\s+func\s+${name}\s*\(`, "m").test(source);
26173
26174
  }
26174
26175
  if (missing.kind === "enum member") {
26175
26176
  return enumValues(source, missing.enum ?? "").includes(missing.member);
26176
26177
  }
26177
- const kinds = missing.kind === "property" ? "var|const|enum|class|signal|func" : "var|const|enum|class|signal";
26178
- return new RegExp(String.raw`^\s*(?:static\s+)?(?:@\w+(?:\([^)]*\))?\s+)*(?:${kinds})\s+${name}\b`, "m").test(source) || enumValues(source, null).includes(missing.member);
26178
+ const kinds = missing.kind === "property" ? String.raw`(?:static\s+)?(?:var|func)|const|enum|class|signal` : String.raw`static\s+(?:var|func)|const|enum|class`;
26179
+ return declared(kinds) || enumValues(source, null).includes(missing.member);
26179
26180
  }
26180
26181
  function enumValues(source, named) {
26181
- const opening = named === null ? /^\s*enum\s*\{([^}]*)\}/gm : new RegExp(String.raw`^\s*enum\s+${named.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\s*\{([^}]*)\}`, "gm");
26182
+ const opening = named === null ? /^enum\s*\{([^}]*)\}/gm : new RegExp(String.raw`^enum\s+${named.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\s*\{([^}]*)\}`, "gm");
26182
26183
  const values = [];
26183
26184
  for (const [, body] of source.matchAll(opening)) {
26184
26185
  const uncommented = (body ?? "").split(`
@@ -26221,21 +26222,22 @@ function classesNamedIn(source, known) {
26221
26222
  return [...named].sort();
26222
26223
  }
26223
26224
  function contradictedDiagnostics(messages, classes, sourceOf) {
26225
+ const keyOf = (missing) => [missing.type, missing.enum ?? "", missing.member].join(".");
26226
+ const denials = messages.map((message) => missingMemberIn(message)).filter((missing) => missing !== null);
26227
+ const calledOnTheClass = new Set(denials.filter((missing) => missing.kind === "static method").map((missing) => keyOf(missing)));
26224
26228
  const read = new Map;
26225
26229
  const found = new Map;
26226
- for (const message of messages) {
26227
- const missing = missingMemberIn(message);
26228
- const declaredIn = missing === null ? undefined : classes.get(missing.type);
26229
- if (missing === null || declaredIn === undefined) {
26230
+ for (const missing of denials) {
26231
+ const declaredIn = classes.get(missing.type);
26232
+ const key = keyOf(missing);
26233
+ if (declaredIn === undefined || missing.kind === "method" && calledOnTheClass.has(key)) {
26230
26234
  continue;
26231
26235
  }
26232
26236
  if (!read.has(declaredIn)) {
26233
26237
  read.set(declaredIn, sourceOf(declaredIn));
26234
26238
  }
26235
26239
  const source = read.get(declaredIn) ?? null;
26236
- const key = [missing.type, missing.enum ?? "", missing.member].join(".");
26237
- const already = found.get(key);
26238
- if (source !== null && declaresMember(source, missing) && already?.kind !== "static method") {
26240
+ if (source !== null && declaresMember(source, missing)) {
26239
26241
  found.set(key, { ...missing, declaredIn });
26240
26242
  }
26241
26243
  }
@@ -26274,7 +26276,8 @@ function classRemedy(entries) {
26274
26276
  }
26275
26277
  const declaring = declaringScripts(entries);
26276
26278
  const reload = declaring.length === 1 ? `editor_rescan with reloadScript set to ${declaring[0]}` : `editor_rescan with reloadScript, a call each for ${declaring.join(" and ")} since it takes one script,`;
26277
- return `For ${entries.map(written).join(", ")}, run ${reload} first. In the one project that has ` + "reproduced a stale enum member, twice, the reload cleared it at once both times, and the one " + "plain editor_rescan tried left the diagnostic standing.";
26279
+ const oneAtATime = declaring.length === 1 ? "" : " Take them one at a time: a rescan that brings in a class also reloads every script naming it and lists them under dependentsReloaded, so a script listed there needs no call of its own.";
26280
+ return `For ${entries.map(written).join(", ")}, run ${reload} first.${oneAtATime} In the one project ` + "that has reproduced a stale enum member, three times, the reload cleared it at once each time, " + "and the one plain editor_rescan tried left the diagnostic standing.";
26278
26281
  }
26279
26282
  function failedReloadNote(failed) {
26280
26283
  const missing = (before, after) => before.filter((name) => !after.includes(name));
@@ -26284,9 +26287,9 @@ function failedReloadNote(failed) {
26284
26287
  ];
26285
26288
  const unchanged = lost.length === 0 && missing(failed.heldAfter, failed.heldBefore).length === 0 && missing(failed.constantsAfter, failed.constantsBefore).length === 0;
26286
26289
  const answered = failed.codeName === "" ? `error ${failed.code}` : `error ${failed.code}, ${failed.codeName}`;
26287
- const copy = unchanged ? "The copy the editor holds is as it was, with the members under heldBeforeReload and heldConstantsBeforeReload." : `The copy the editor holds changed even so${lost.length === 0 ? "" : `, and has lost ${lost.join(", ")}`}: anything diagnosed against it now is answered from what is left, and reloading it once it compiles rebuilds it.`;
26290
+ const copy = unchanged ? "Nothing was reloaded: the copy the editor holds is as it was, with the members under heldBeforeReload and heldConstantsBeforeReload." : `The copy the editor holds changed even so${lost.length === 0 ? "" : `, and has lost ${lost.join(", ")}`}: anything diagnosed against it now is answered from what is left, and reloading it once it compiles rebuilds it.`;
26288
26291
  const order = failed.names.length === 0 ? "" : ` If they deny a member of ${failed.names.join(" or ")} that its file declares, the editor's copy of that class is behind: reload that class first with reloadScript and then this script, the order that cleared it in the one project that has met this.`;
26289
- return `${failed.script} did not compile, so nothing was reloaded: Godot answered ${answered}. ${copy} ` + `script_diagnostics on it gives the errors.${order}`;
26292
+ return `${failed.script} did not compile: Godot answered ${answered}. ${copy} ` + `script_diagnostics on it gives the errors.${order}`;
26290
26293
  }
26291
26294
  function uncachedClassNote(types) {
26292
26295
  if (types.length === 0) {
@@ -28146,14 +28149,34 @@ class GodotLocator {
28146
28149
 
28147
28150
  // src/headless.ts
28148
28151
  import { execFile as execFile3 } from "node:child_process";
28149
- import { writeFileSync as writeFileSync7 } from "node:fs";
28150
- import { join as join12 } from "node:path";
28152
+ import { writeFileSync as writeFileSync8 } from "node:fs";
28153
+ import { join as join13 } from "node:path";
28151
28154
  import { promisify as promisify3 } from "node:util";
28152
28155
 
28156
+ // src/reimport.ts
28157
+ import { createHash as createHash2 } from "node:crypto";
28158
+ import { existsSync as existsSync8, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync5, utimesSync, writeFileSync as writeFileSync7 } from "node:fs";
28159
+ import { basename as basename2, join as join11 } from "node:path";
28160
+ function forceNextImport(projectPath, resourcePath) {
28161
+ const relative = resourcePath.replace(/^res:\/\//, "");
28162
+ const sidecar = join11(projectPath, `${relative}.import`);
28163
+ if (!existsSync8(sidecar)) {
28164
+ return;
28165
+ }
28166
+ const { atime, mtimeMs } = statSync5(sidecar);
28167
+ writeFileSync7(sidecar, readFileSync11(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
28168
+ utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
28169
+ rmSync4(importRecord(projectPath, resourcePath), { force: true });
28170
+ }
28171
+ function importRecord(projectPath, resourcePath) {
28172
+ const digest = createHash2("md5").update(resourcePath).digest("hex");
28173
+ return join11(projectPath, ".godot", "imported", `${basename2(resourcePath)}-${digest}.md5`);
28174
+ }
28175
+
28153
28176
  // src/scratch.ts
28154
- import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
28177
+ import { existsSync as existsSync9, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
28155
28178
  import { tmpdir as tmpdir3 } from "node:os";
28156
- import { join as join11 } from "node:path";
28179
+ import { join as join12 } from "node:path";
28157
28180
  import process7 from "node:process";
28158
28181
  var SWEPT = { recursive: true, force: true };
28159
28182
  var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
@@ -28168,7 +28191,7 @@ var KINDS = [
28168
28191
  "tests"
28169
28192
  ];
28170
28193
  function scratchDirectory(kind) {
28171
- return mkdtempSync(join11(tmpdir3(), `gdharness-${kind}-${process7.pid}-`));
28194
+ return mkdtempSync(join12(tmpdir3(), `gdharness-${kind}-${process7.pid}-`));
28172
28195
  }
28173
28196
  function sweepAbandonedScratch(directory = tmpdir3()) {
28174
28197
  let entries;
@@ -28184,7 +28207,7 @@ function sweepAbandonedScratch(directory = tmpdir3()) {
28184
28207
  continue;
28185
28208
  }
28186
28209
  try {
28187
- remove(join11(directory, entry));
28210
+ remove(join12(directory, entry));
28188
28211
  } catch {}
28189
28212
  }
28190
28213
  }
@@ -28210,7 +28233,7 @@ function discard(...paths) {
28210
28233
  return left;
28211
28234
  }
28212
28235
  function remove(path) {
28213
- rmSync4(path, SWEPT);
28236
+ rmSync5(path, SWEPT);
28214
28237
  }
28215
28238
  function tryAgain(path, attempt) {
28216
28239
  if (attempt === 0) {
@@ -28222,7 +28245,7 @@ function tryAgain(path, attempt) {
28222
28245
  lastTryOnExit();
28223
28246
  }
28224
28247
  const after = AGAIN_AFTER_MS[attempt];
28225
- if (after === undefined || !existsSync8(path)) {
28248
+ if (after === undefined || !existsSync9(path)) {
28226
28249
  waiting.delete(path);
28227
28250
  if (after === undefined) {
28228
28251
  atExit.add(path);
@@ -28302,21 +28325,36 @@ function librariesNotCopied(messages) {
28302
28325
  }
28303
28326
  return [...found];
28304
28327
  }
28305
- function extensionRetryNote(libraries) {
28306
- return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + "editor's copy aside, so the import was run again and loaded the extension, and the messages are from " + "that second run. The editor's copy is left beside the library as a ~RF….TMP file, which can be deleted " + "once the editor has exited.";
28328
+ function extensionRetryNote(libraries, stillNotLoaded = []) {
28329
+ const second = stillNotLoaded.length === 0 ? "so the import was run again and loaded the extension, and the messages are from that second run." : `so the import was run again, and it still could not load ${stillNotLoaded.join(" and ")}: this answer is from an import without ${stillNotLoaded.length === 1 ? "it" : "them"}, and its messages say what the engine hit.`;
28330
+ return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "can be deleted once the editor has exited.";
28307
28331
  }
28308
- async function runImport(godotPath, projectPath, once = importOnce) {
28309
- const first = await once(godotPath, projectPath);
28332
+ async function runImport(godotPath, projectPath, options = {}) {
28333
+ const once = options.once ?? importOnce;
28334
+ const pass = () => {
28335
+ for (const target of options.forcing ?? []) {
28336
+ forceNextImport(projectPath, target);
28337
+ }
28338
+ return once(godotPath, projectPath);
28339
+ };
28340
+ const first = await pass();
28310
28341
  const collided = librariesNotCopied(first.messages);
28311
28342
  if (collided.length === 0) {
28312
28343
  return first;
28313
28344
  }
28314
- return { ...await once(godotPath, projectPath), librariesRetried: collided };
28345
+ const second = await pass();
28346
+ const still = librariesNotCopied(second.messages);
28347
+ return {
28348
+ ...second,
28349
+ librariesRetried: collided,
28350
+ ...still.length === 0 ? {} : { librariesNotLoaded: still },
28351
+ extensionNote: extensionRetryNote(collided, still)
28352
+ };
28315
28353
  }
28316
28354
  async function importOnce(godotPath, projectPath) {
28317
28355
  const logDir = scratchDirectory("import");
28318
28356
  try {
28319
- const { stderr } = await run3(godotPath, ["--headless", "--log-file", join12(logDir, "engine.log"), "--path", projectPath, "--import"], { signal: callSignal() });
28357
+ const { stderr } = await run3(godotPath, ["--headless", "--log-file", join13(logDir, "engine.log"), "--path", projectPath, "--import"], { signal: callSignal() });
28320
28358
  return { ok: true, messages: problems(stderr) };
28321
28359
  } catch (error) {
28322
28360
  if (error instanceof Error && "stdout" in error && "stderr" in error) {
@@ -28338,12 +28376,12 @@ async function importOnce(godotPath, projectPath) {
28338
28376
  }
28339
28377
  async function runOperation(engine, operation, params, projectPath) {
28340
28378
  const paramsDir = scratchDirectory("params");
28341
- const paramsFile = join12(paramsDir, `${operation}.json`);
28342
- writeFileSync7(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
28379
+ const paramsFile = join13(paramsDir, `${operation}.json`);
28380
+ writeFileSync8(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
28343
28381
  const args = [
28344
28382
  "--headless",
28345
28383
  "--log-file",
28346
- join12(paramsDir, "engine.log"),
28384
+ join13(paramsDir, "engine.log"),
28347
28385
  "--path",
28348
28386
  projectPath,
28349
28387
  "--script",
@@ -29399,14 +29437,14 @@ function secondsFromClock(printed) {
29399
29437
  }
29400
29438
 
29401
29439
  // src/project-scan.ts
29402
- import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync11 } from "node:fs";
29403
- import { join as join13 } from "node:path";
29440
+ import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "node:fs";
29441
+ import { join as join14 } from "node:path";
29404
29442
  var SKIPPED = new Set(["node_modules"]);
29405
29443
  function skipped(name) {
29406
29444
  return name.startsWith(".") || SKIPPED.has(name);
29407
29445
  }
29408
29446
  function steppedOver(directory) {
29409
- return existsSync9(join13(directory, ".gdignore"));
29447
+ return existsSync10(join14(directory, ".gdignore"));
29410
29448
  }
29411
29449
  var ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
29412
29450
  function projectStructure(projectPath) {
@@ -29420,7 +29458,7 @@ function projectStructure(projectPath) {
29420
29458
  continue;
29421
29459
  }
29422
29460
  if (entry.isDirectory()) {
29423
- visit(join13(directory, entry.name));
29461
+ visit(join14(directory, entry.name));
29424
29462
  } else if (entry.isFile()) {
29425
29463
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
29426
29464
  if (extension === "tscn") {
@@ -29451,14 +29489,14 @@ function scriptsWithoutUid(projectPath) {
29451
29489
  }
29452
29490
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
29453
29491
  if (entry.isDirectory()) {
29454
- visit(join13(directory, entry.name), spelled);
29492
+ visit(join14(directory, entry.name), spelled);
29455
29493
  continue;
29456
29494
  }
29457
29495
  if (!entry.isFile()) {
29458
29496
  continue;
29459
29497
  }
29460
29498
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
29461
- if (CARRIES_A_UID.has(extension) && !existsSync9(join13(directory, `${entry.name}.uid`))) {
29499
+ if (CARRIES_A_UID.has(extension) && !existsSync10(join14(directory, `${entry.name}.uid`))) {
29462
29500
  found.push(spelled);
29463
29501
  }
29464
29502
  }
@@ -29493,7 +29531,7 @@ function searchProject(projectPath, options) {
29493
29531
  if (skipped(entry.name)) {
29494
29532
  continue;
29495
29533
  }
29496
- const entryPath = join13(directory, entry.name);
29534
+ const entryPath = join14(directory, entry.name);
29497
29535
  if (entry.isDirectory()) {
29498
29536
  visit(entryPath);
29499
29537
  continue;
@@ -29504,7 +29542,7 @@ function searchProject(projectPath, options) {
29504
29542
  }
29505
29543
  result.summary.files_searched += 1;
29506
29544
  const matches = [];
29507
- for (const [index, line] of readFileSync11(entryPath, "utf8").split(`
29545
+ for (const [index, line] of readFileSync12(entryPath, "utf8").split(`
29508
29546
  `).entries()) {
29509
29547
  if (full()) {
29510
29548
  break;
@@ -29526,26 +29564,6 @@ function searchProject(projectPath, options) {
29526
29564
  return result;
29527
29565
  }
29528
29566
 
29529
- // src/reimport.ts
29530
- import { createHash as createHash2 } from "node:crypto";
29531
- import { existsSync as existsSync10, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync5, utimesSync, writeFileSync as writeFileSync8 } from "node:fs";
29532
- import { basename as basename2, join as join14 } from "node:path";
29533
- function forceNextImport(projectPath, resourcePath) {
29534
- const relative = resourcePath.replace(/^res:\/\//, "");
29535
- const sidecar = join14(projectPath, `${relative}.import`);
29536
- if (!existsSync10(sidecar)) {
29537
- return;
29538
- }
29539
- const { atime, mtimeMs } = statSync5(sidecar);
29540
- writeFileSync8(sidecar, readFileSync12(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
29541
- utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
29542
- rmSync5(importRecord(projectPath, resourcePath), { force: true });
29543
- }
29544
- function importRecord(projectPath, resourcePath) {
29545
- const digest = createHash2("md5").update(resourcePath).digest("hex");
29546
- return join14(projectPath, ".godot", "imported", `${basename2(resourcePath)}-${digest}.md5`);
29547
- }
29548
-
29549
29567
  // src/resources.ts
29550
29568
  import { readFileSync as readFileSync13 } from "node:fs";
29551
29569
  import { extname, resolve as resolve4 } from "node:path";
@@ -31411,7 +31429,7 @@ var TOOL_SPECS = [
31411
31429
  },
31412
31430
  value: {
31413
31431
  ops: ["until"],
31414
- description: "until: the value to wait for, fitted to the property's type. On a property declared to hold an object, null, which the text null also means; any other value is refused, since the property can never hold it."
31432
+ description: "until: the value to wait for, fitted to the property's type. On a property holding an object, whether declared to or untyped and holding one or null, the text null means null. One declared to hold an object refuses any value other than null, since it can never hold it."
31415
31433
  },
31416
31434
  says: {
31417
31435
  type: "string",
@@ -33110,7 +33128,7 @@ class GodotServer {
33110
33128
  uidsCreated: given,
33111
33129
  stillWithoutUid: after,
33112
33130
  note: uidsLeftNote(after.length),
33113
- ...imported.librariesRetried === undefined ? {} : { extensionNote: extensionRetryNote(imported.librariesRetried) }
33131
+ ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
33114
33132
  }
33115
33133
  }, scanned));
33116
33134
  }
@@ -33255,19 +33273,16 @@ class GodotServer {
33255
33273
  if (!engine.ok) {
33256
33274
  return { ok: false, response: engine.response };
33257
33275
  }
33258
- for (const target of targets) {
33259
- forceNextImport(projectPath, target);
33260
- }
33261
33276
  const scanned = await this.untilTheEditorsScanIsWritten(projectPath);
33262
- const imported = await runImport(engine.value, projectPath);
33277
+ const imported = await runImport(engine.value, projectPath, { forcing: targets });
33263
33278
  if (!imported.ok) {
33264
33279
  return { ok: false, response: this.answer(withScanWait(imported, scanned)) };
33265
33280
  }
33266
33281
  if (imported.messages.length > 0) {
33267
33282
  extra["engine_messages"] = imported.messages;
33268
33283
  }
33269
- if (imported.librariesRetried !== undefined) {
33270
- extra["extensionNote"] = extensionRetryNote(imported.librariesRetried);
33284
+ if (imported.extensionNote !== undefined) {
33285
+ extra["extensionNote"] = imported.extensionNote;
33271
33286
  }
33272
33287
  }
33273
33288
  const after = await this.operation("get_import_status", resourcePath === undefined ? { includeUpToDate: true } : { resourcePath }, projectPath);
@@ -33758,7 +33773,7 @@ class GodotServer {
33758
33773
  const notes = [];
33759
33774
  if (contradicted.length > 0) {
33760
33775
  const dependsOn = new Set;
33761
- for (const one of contradicted) {
33776
+ for (const one of contradicted.filter((entry) => entry.kind === "method" || entry.kind === "property")) {
33762
33777
  const source = cached === null ? null : this.sourceOfClass(projectPath, one.declaredIn);
33763
33778
  if (source === null || cached === null) {
33764
33779
  continue;
@@ -33798,9 +33813,10 @@ class GodotServer {
33798
33813
  });
33799
33814
  }
33800
33815
  classesAScriptNames(projectPath, scriptPath) {
33801
- const source = this.sourceOfClass(projectPath, scriptPath);
33816
+ const resourcePath = scriptPath.startsWith("res://") ? scriptPath : `res://${scriptPath.replace(/^\/+/, "")}`;
33817
+ const source = this.sourceOfClass(projectPath, resourcePath);
33802
33818
  const known = cachedClasses(projectPath) ?? declaredClasses(projectPath);
33803
- return source === null ? [] : classesNamedIn(source, known).filter((name) => known.get(name) !== scriptPath);
33819
+ return source === null ? [] : classesNamedIn(source, known).filter((name) => known.get(name) !== resourcePath);
33804
33820
  }
33805
33821
  sourceOfClass(projectPath, resourcePath) {
33806
33822
  const contained = resolveWithinProject(projectPath, resourcePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
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",