gdharness 1.0.6 → 1.0.8
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 +167 -79
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +28 -5
- package/build/index.js +180 -94
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -15694,7 +15694,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15694
15694
|
requires: []
|
|
15695
15695
|
},
|
|
15696
15696
|
refresh_classes: {
|
|
15697
|
-
summary: 'rewrite .godot/global_script_class_cache.cfg from the class_name declarations on disk, and name under unseenByEditor any class the editor open on this project still cannot resolve: rewriting the file does not reach the list a running editor loaded, so "added: []" means the file was already right rather than that nothing is wrong',
|
|
15697
|
+
summary: 'rewrite .godot/global_script_class_cache.cfg from the class_name declarations on disk, and name under unseenByEditor any class the editor open on this project still cannot resolve: rewriting the file does not reach the list a running editor loaded, so "added: []" means the file was already right rather than that nothing is wrong. A class the cache held after the last rebuild and had lost when this one began is named under lostSinceLastRebuild: the editor wrote the file without it in between, on a save or a scan, and will again until restarted',
|
|
15698
15698
|
requires: []
|
|
15699
15699
|
}
|
|
15700
15700
|
}
|
|
@@ -15719,7 +15719,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15719
15719
|
},
|
|
15720
15720
|
{
|
|
15721
15721
|
name: "project_test",
|
|
15722
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines. The class list is rebuilt first, so a suite written a moment ago is found. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. Needs gdUnit4 under addons/gdUnit4.",
|
|
15722
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. Needs gdUnit4 under addons/gdUnit4.",
|
|
15723
15723
|
parameters: {
|
|
15724
15724
|
projectPath: PROJECT_PATH,
|
|
15725
15725
|
path: {
|
|
@@ -15936,7 +15936,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15936
15936
|
},
|
|
15937
15937
|
{
|
|
15938
15938
|
name: "script_diagnostics",
|
|
15939
|
-
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
|
|
15939
|
+
description: "Errors and warnings for a script from the editor's language server, and whether the script is clean. Needs the editor running. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares comes back under contradictedByTheFile, and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. The one exception is named when it applies: an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
|
|
15940
15940
|
parameters: {
|
|
15941
15941
|
projectPath: PROJECT_PATH,
|
|
15942
15942
|
scriptPath: SCRIPT_PATH
|
|
@@ -31993,17 +31993,49 @@ var init_bridge_announce = __esm(() => {
|
|
|
31993
31993
|
init_errors();
|
|
31994
31994
|
});
|
|
31995
31995
|
|
|
31996
|
+
// src/class-note.ts
|
|
31997
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
|
|
31998
|
+
import { dirname as dirname11, join as join16 } from "node:path";
|
|
31999
|
+
function classNotePath(projectPath) {
|
|
32000
|
+
return join16(projectPath, ".godot", "gdharness-classes.json");
|
|
32001
|
+
}
|
|
32002
|
+
function readClassNote(projectPath) {
|
|
32003
|
+
try {
|
|
32004
|
+
const fields = asParams(JSON.parse(readFileSync14(classNotePath(projectPath), "utf8")));
|
|
32005
|
+
const rebuiltWith = readArray(fields, "rebuiltWith");
|
|
32006
|
+
if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
|
|
32007
|
+
return null;
|
|
32008
|
+
}
|
|
32009
|
+
const pid = readNumber(fields, "shortListEditorPid");
|
|
32010
|
+
return {
|
|
32011
|
+
rebuiltWith: rebuiltWith.map(String),
|
|
32012
|
+
...Object.hasOwn(fields, "shortListEditorPid") ? { shortListEditorPid: pid ?? null } : {}
|
|
32013
|
+
};
|
|
32014
|
+
} catch {
|
|
32015
|
+
return null;
|
|
32016
|
+
}
|
|
32017
|
+
}
|
|
32018
|
+
function writeClassNote(projectPath, note) {
|
|
32019
|
+
try {
|
|
32020
|
+
const path = classNotePath(projectPath);
|
|
32021
|
+
mkdirSync9(dirname11(path), { recursive: true });
|
|
32022
|
+
writeFileSync11(path, `${JSON.stringify(note, null, 2)}
|
|
32023
|
+
`, "utf8");
|
|
32024
|
+
} catch {}
|
|
32025
|
+
}
|
|
32026
|
+
var init_class_note = () => {};
|
|
32027
|
+
|
|
31996
32028
|
// src/config-pin.ts
|
|
31997
|
-
import { existsSync as existsSync11, readFileSync as
|
|
31998
|
-
import { join as
|
|
32029
|
+
import { existsSync as existsSync11, readFileSync as readFileSync15 } from "node:fs";
|
|
32030
|
+
import { join as join17 } from "node:path";
|
|
31999
32031
|
function versionsNamedInConfig(projectPath) {
|
|
32000
|
-
const config =
|
|
32032
|
+
const config = join17(projectPath, ".mcp.json");
|
|
32001
32033
|
if (!existsSync11(config)) {
|
|
32002
32034
|
return [];
|
|
32003
32035
|
}
|
|
32004
32036
|
let text;
|
|
32005
32037
|
try {
|
|
32006
|
-
text =
|
|
32038
|
+
text = readFileSync15(config, "utf8");
|
|
32007
32039
|
JSON.parse(text);
|
|
32008
32040
|
} catch {
|
|
32009
32041
|
return [];
|
|
@@ -36564,7 +36596,7 @@ var init_junit = __esm(() => {
|
|
|
36564
36596
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
36565
36597
|
import { readFile, realpath } from "node:fs/promises";
|
|
36566
36598
|
import { createConnection as createConnection3 } from "node:net";
|
|
36567
|
-
import { dirname as
|
|
36599
|
+
import { dirname as dirname12, resolve as resolve4 } from "node:path";
|
|
36568
36600
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
36569
36601
|
function diagnosticsKey(uri) {
|
|
36570
36602
|
let decoded;
|
|
@@ -36692,7 +36724,7 @@ class GodotLSPClient {
|
|
|
36692
36724
|
if (this.initialized) {
|
|
36693
36725
|
return;
|
|
36694
36726
|
}
|
|
36695
|
-
const rootPath = this.rootPath ??
|
|
36727
|
+
const rootPath = this.rootPath ?? dirname12(resolve4(filePath));
|
|
36696
36728
|
await this.initialize(rootPath);
|
|
36697
36729
|
}
|
|
36698
36730
|
async sendRequest(method, params) {
|
|
@@ -37137,13 +37169,13 @@ var init_process_time = __esm(() => {
|
|
|
37137
37169
|
});
|
|
37138
37170
|
|
|
37139
37171
|
// src/project-scan.ts
|
|
37140
|
-
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as
|
|
37141
|
-
import { join as
|
|
37172
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync16 } from "node:fs";
|
|
37173
|
+
import { join as join18 } from "node:path";
|
|
37142
37174
|
function skipped(name) {
|
|
37143
37175
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
37144
37176
|
}
|
|
37145
37177
|
function steppedOver(directory) {
|
|
37146
|
-
return existsSync13(
|
|
37178
|
+
return existsSync13(join18(directory, ".gdignore"));
|
|
37147
37179
|
}
|
|
37148
37180
|
function projectStructure(projectPath) {
|
|
37149
37181
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -37156,7 +37188,7 @@ function projectStructure(projectPath) {
|
|
|
37156
37188
|
continue;
|
|
37157
37189
|
}
|
|
37158
37190
|
if (entry.isDirectory()) {
|
|
37159
|
-
visit(
|
|
37191
|
+
visit(join18(directory, entry.name));
|
|
37160
37192
|
} else if (entry.isFile()) {
|
|
37161
37193
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37162
37194
|
if (extension === "tscn") {
|
|
@@ -37186,14 +37218,14 @@ function scriptsWithoutUid(projectPath) {
|
|
|
37186
37218
|
}
|
|
37187
37219
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
37188
37220
|
if (entry.isDirectory()) {
|
|
37189
|
-
visit(
|
|
37221
|
+
visit(join18(directory, entry.name), spelled);
|
|
37190
37222
|
continue;
|
|
37191
37223
|
}
|
|
37192
37224
|
if (!entry.isFile()) {
|
|
37193
37225
|
continue;
|
|
37194
37226
|
}
|
|
37195
37227
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
37196
|
-
if (CARRIES_A_UID.has(extension) && !existsSync13(
|
|
37228
|
+
if (CARRIES_A_UID.has(extension) && !existsSync13(join18(directory, `${entry.name}.uid`))) {
|
|
37197
37229
|
found.push(spelled);
|
|
37198
37230
|
}
|
|
37199
37231
|
}
|
|
@@ -37228,7 +37260,7 @@ function searchProject(projectPath, options) {
|
|
|
37228
37260
|
if (skipped(entry.name)) {
|
|
37229
37261
|
continue;
|
|
37230
37262
|
}
|
|
37231
|
-
const entryPath =
|
|
37263
|
+
const entryPath = join18(directory, entry.name);
|
|
37232
37264
|
if (entry.isDirectory()) {
|
|
37233
37265
|
visit(entryPath);
|
|
37234
37266
|
continue;
|
|
@@ -37239,7 +37271,7 @@ function searchProject(projectPath, options) {
|
|
|
37239
37271
|
}
|
|
37240
37272
|
result.summary.files_searched += 1;
|
|
37241
37273
|
const matches = [];
|
|
37242
|
-
for (const [index, line] of
|
|
37274
|
+
for (const [index, line] of readFileSync16(entryPath, "utf8").split(`
|
|
37243
37275
|
`).entries()) {
|
|
37244
37276
|
if (full()) {
|
|
37245
37277
|
break;
|
|
@@ -37268,16 +37300,16 @@ var init_project_scan = __esm(() => {
|
|
|
37268
37300
|
});
|
|
37269
37301
|
|
|
37270
37302
|
// src/restart-note.ts
|
|
37271
|
-
import { mkdirSync as
|
|
37272
|
-
import { dirname as
|
|
37303
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "node:fs";
|
|
37304
|
+
import { dirname as dirname13, join as join19 } from "node:path";
|
|
37273
37305
|
function restartNotePath(projectPath) {
|
|
37274
|
-
return
|
|
37306
|
+
return join19(projectPath, ".godot", "gdharness-restart.json");
|
|
37275
37307
|
}
|
|
37276
37308
|
function noteRestartBegun(note) {
|
|
37277
37309
|
const path = restartNotePath(note.projectPath);
|
|
37278
37310
|
try {
|
|
37279
|
-
|
|
37280
|
-
|
|
37311
|
+
mkdirSync10(dirname13(path), { recursive: true });
|
|
37312
|
+
writeFileSync12(path, `${JSON.stringify(note, null, 2)}
|
|
37281
37313
|
`, "utf8");
|
|
37282
37314
|
} catch (error) {
|
|
37283
37315
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -37285,7 +37317,7 @@ function noteRestartBegun(note) {
|
|
|
37285
37317
|
}
|
|
37286
37318
|
function restartOwed(projectPath) {
|
|
37287
37319
|
try {
|
|
37288
|
-
const parsed = JSON.parse(
|
|
37320
|
+
const parsed = JSON.parse(readFileSync17(restartNotePath(projectPath), "utf8"));
|
|
37289
37321
|
if (typeof parsed !== "object" || parsed === null) {
|
|
37290
37322
|
return null;
|
|
37291
37323
|
}
|
|
@@ -37311,36 +37343,36 @@ var init_restart_note = __esm(() => {
|
|
|
37311
37343
|
|
|
37312
37344
|
// src/run-record.ts
|
|
37313
37345
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
37314
|
-
import { mkdirSync as
|
|
37315
|
-
import { basename as basename3, join as
|
|
37346
|
+
import { mkdirSync as mkdirSync11, openSync, readdirSync as readdirSync6, readFileSync as readFileSync18, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
|
|
37347
|
+
import { basename as basename3, join as join20 } from "node:path";
|
|
37316
37348
|
function runsDirectory() {
|
|
37317
|
-
return
|
|
37349
|
+
return join20(runtimeDirectory(), "runs");
|
|
37318
37350
|
}
|
|
37319
37351
|
function recordPath() {
|
|
37320
|
-
return
|
|
37352
|
+
return join20(runsDirectory(), "run.json");
|
|
37321
37353
|
}
|
|
37322
37354
|
function recordPaths() {
|
|
37323
|
-
return runtimeDirectories().map((directory) =>
|
|
37355
|
+
return runtimeDirectories().map((directory) => join20(directory, "runs", "run.json"));
|
|
37324
37356
|
}
|
|
37325
37357
|
function openTranscript(startedAt) {
|
|
37326
|
-
|
|
37327
|
-
const path =
|
|
37358
|
+
mkdirSync11(runsDirectory(), { recursive: true });
|
|
37359
|
+
const path = join20(runsDirectory(), `run-${startedAt}.log`);
|
|
37328
37360
|
return { path, fd: openSync(path, "a") };
|
|
37329
37361
|
}
|
|
37330
37362
|
function writeRunRecord(record) {
|
|
37331
|
-
|
|
37332
|
-
|
|
37363
|
+
mkdirSync11(runsDirectory(), { recursive: true });
|
|
37364
|
+
writeFileSync13(recordPath(), JSON.stringify(record, null, 2), "utf8");
|
|
37333
37365
|
}
|
|
37334
37366
|
function editorNotePath() {
|
|
37335
|
-
return
|
|
37367
|
+
return join20(runsDirectory(), "editor-run.json");
|
|
37336
37368
|
}
|
|
37337
37369
|
function writeEditorRunNote(note) {
|
|
37338
|
-
|
|
37339
|
-
|
|
37370
|
+
mkdirSync11(runsDirectory(), { recursive: true });
|
|
37371
|
+
writeFileSync13(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
|
|
37340
37372
|
}
|
|
37341
37373
|
function readEditorRunNote() {
|
|
37342
37374
|
for (const directory of runtimeDirectories()) {
|
|
37343
|
-
const note = editorNoteAt(
|
|
37375
|
+
const note = editorNoteAt(join20(directory, "runs", "editor-run.json"));
|
|
37344
37376
|
if (note !== null) {
|
|
37345
37377
|
return note;
|
|
37346
37378
|
}
|
|
@@ -37350,7 +37382,7 @@ function readEditorRunNote() {
|
|
|
37350
37382
|
function editorNoteAt(path) {
|
|
37351
37383
|
let parsed;
|
|
37352
37384
|
try {
|
|
37353
|
-
parsed = JSON.parse(
|
|
37385
|
+
parsed = JSON.parse(readFileSync18(path, "utf8"));
|
|
37354
37386
|
} catch {
|
|
37355
37387
|
return null;
|
|
37356
37388
|
}
|
|
@@ -37373,7 +37405,7 @@ function recordRunEnded(pid, exitCode) {
|
|
|
37373
37405
|
return;
|
|
37374
37406
|
}
|
|
37375
37407
|
try {
|
|
37376
|
-
|
|
37408
|
+
writeFileSync13(path, JSON.stringify({ ...record, exitCode }, null, 2), "utf8");
|
|
37377
37409
|
} catch {}
|
|
37378
37410
|
}
|
|
37379
37411
|
function readRunRecord() {
|
|
@@ -37388,7 +37420,7 @@ function readRunRecord() {
|
|
|
37388
37420
|
function recordAt(path) {
|
|
37389
37421
|
let parsed;
|
|
37390
37422
|
try {
|
|
37391
|
-
parsed = JSON.parse(
|
|
37423
|
+
parsed = JSON.parse(readFileSync18(path, "utf8"));
|
|
37392
37424
|
} catch {
|
|
37393
37425
|
return null;
|
|
37394
37426
|
}
|
|
@@ -37433,7 +37465,7 @@ function runningAs(pid) {
|
|
|
37433
37465
|
const began = startedAt(pid, null);
|
|
37434
37466
|
const when = began === null ? {} : { startedAt: began };
|
|
37435
37467
|
if (process.platform === "linux") {
|
|
37436
|
-
const raw =
|
|
37468
|
+
const raw = readFileSync18(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
37437
37469
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
37438
37470
|
}
|
|
37439
37471
|
const args = execFileSync2("ps", ["-p", String(pid), "-o", "args="], {
|
|
@@ -37584,7 +37616,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
37584
37616
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
37585
37617
|
continue;
|
|
37586
37618
|
}
|
|
37587
|
-
const path =
|
|
37619
|
+
const path = join20(directory, entry);
|
|
37588
37620
|
if (path === keep || ageMs(path, now) < keepMs) {
|
|
37589
37621
|
continue;
|
|
37590
37622
|
}
|
|
@@ -37607,16 +37639,16 @@ import {
|
|
|
37607
37639
|
closeSync,
|
|
37608
37640
|
existsSync as existsSync14,
|
|
37609
37641
|
fstatSync,
|
|
37610
|
-
mkdirSync as
|
|
37642
|
+
mkdirSync as mkdirSync12,
|
|
37611
37643
|
mkdtempSync as mkdtempSync3,
|
|
37612
37644
|
openSync as openSync2,
|
|
37613
37645
|
readdirSync as readdirSync7,
|
|
37614
|
-
readFileSync as
|
|
37646
|
+
readFileSync as readFileSync19,
|
|
37615
37647
|
readSync,
|
|
37616
37648
|
statSync as statSync5
|
|
37617
37649
|
} from "node:fs";
|
|
37618
37650
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37619
|
-
import { basename as basename4, dirname as
|
|
37651
|
+
import { basename as basename4, dirname as dirname14, join as join21, normalize as normalize3, relative as relative3 } from "node:path";
|
|
37620
37652
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
37621
37653
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
37622
37654
|
import { promisify as promisify7 } from "node:util";
|
|
@@ -37858,7 +37890,7 @@ function aboveTheRunner(entry) {
|
|
|
37858
37890
|
}
|
|
37859
37891
|
function elsewhereIn(projectPath, asked) {
|
|
37860
37892
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
37861
|
-
if (wanted === "" || existsSync14(
|
|
37893
|
+
if (wanted === "" || existsSync14(join21(projectPath, wanted))) {
|
|
37862
37894
|
return "";
|
|
37863
37895
|
}
|
|
37864
37896
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -37872,7 +37904,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
37872
37904
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
37873
37905
|
}
|
|
37874
37906
|
function hasMainScene(projectFile) {
|
|
37875
|
-
const scene = parseProjectGodot(
|
|
37907
|
+
const scene = parseProjectGodot(readFileSync19(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
37876
37908
|
return typeof scene === "string" && scene !== "";
|
|
37877
37909
|
}
|
|
37878
37910
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -37880,13 +37912,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
37880
37912
|
return false;
|
|
37881
37913
|
}
|
|
37882
37914
|
try {
|
|
37883
|
-
return
|
|
37915
|
+
return readFileSync19(join21(projectPath, RUNTIME_AUTOLOAD2.path), "utf8").includes('"editor_pid"');
|
|
37884
37916
|
} catch {
|
|
37885
37917
|
return false;
|
|
37886
37918
|
}
|
|
37887
37919
|
}
|
|
37888
37920
|
function editorPlaysHeadless(projectFile) {
|
|
37889
|
-
const runArgs = parseProjectGodot(
|
|
37921
|
+
const runArgs = parseProjectGodot(readFileSync19(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
37890
37922
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
37891
37923
|
}
|
|
37892
37924
|
function camelCased(params) {
|
|
@@ -37902,7 +37934,7 @@ function camelCased(params) {
|
|
|
37902
37934
|
class GodotServer {
|
|
37903
37935
|
mcp;
|
|
37904
37936
|
locator = new GodotLocator2;
|
|
37905
|
-
operationsScript =
|
|
37937
|
+
operationsScript = join21(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
37906
37938
|
godotBridge;
|
|
37907
37939
|
tools = buildToolDefinitions();
|
|
37908
37940
|
activeProcess = null;
|
|
@@ -38333,8 +38365,15 @@ class GodotServer {
|
|
|
38333
38365
|
const contained = this.containProjectFiles(asks);
|
|
38334
38366
|
return contained.ok ? await this.handleViaBridge(editorSide, contained.value) : contained.response;
|
|
38335
38367
|
}
|
|
38336
|
-
|
|
38337
|
-
|
|
38368
|
+
if (headless !== "refresh_class_cache") {
|
|
38369
|
+
return await this.headless(headless, answerable);
|
|
38370
|
+
}
|
|
38371
|
+
const project = this.project(answerable);
|
|
38372
|
+
if (!project.ok) {
|
|
38373
|
+
return project.response;
|
|
38374
|
+
}
|
|
38375
|
+
const answered = this.answer(await this.rebuildClassCache(project.value.path));
|
|
38376
|
+
return this.godotBridge.isConnected() ? await this.alsoSayWhatTheEditorCannotSee(answered, args) : answered;
|
|
38338
38377
|
}
|
|
38339
38378
|
const { op: _op, ...arguments_ } = args;
|
|
38340
38379
|
const bridge = async (command, extra = {}) => {
|
|
@@ -38560,7 +38599,7 @@ class GodotServer {
|
|
|
38560
38599
|
if (path === undefined) {
|
|
38561
38600
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
38562
38601
|
}
|
|
38563
|
-
const file =
|
|
38602
|
+
const file = join21(path, "project.godot");
|
|
38564
38603
|
if (!existsSync14(file)) {
|
|
38565
38604
|
return {
|
|
38566
38605
|
ok: false,
|
|
@@ -38784,7 +38823,7 @@ class GodotServer {
|
|
|
38784
38823
|
if (!engine.ok) {
|
|
38785
38824
|
return engine.response;
|
|
38786
38825
|
}
|
|
38787
|
-
const application = parseProjectGodot(
|
|
38826
|
+
const application = parseProjectGodot(readFileSync19(project.value.file, "utf8"))["application"] ?? {};
|
|
38788
38827
|
const name = application["config/name"];
|
|
38789
38828
|
const mainScene = application["run/main_scene"];
|
|
38790
38829
|
const info = {
|
|
@@ -38845,18 +38884,18 @@ class GodotServer {
|
|
|
38845
38884
|
if (!engine.ok) {
|
|
38846
38885
|
return engine.response;
|
|
38847
38886
|
}
|
|
38848
|
-
const folder =
|
|
38887
|
+
const folder = dirname14(output.absolutePath);
|
|
38849
38888
|
try {
|
|
38850
|
-
|
|
38889
|
+
mkdirSync12(folder, { recursive: true });
|
|
38851
38890
|
} catch (error) {
|
|
38852
|
-
return this.createErrorResponse(`Cannot create ${
|
|
38891
|
+
return this.createErrorResponse(`Cannot create ${dirname14(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
38853
38892
|
}
|
|
38854
38893
|
const debug = readBoolean(args, "debug") ?? false;
|
|
38855
|
-
const exportLogs = mkdtempSync3(
|
|
38894
|
+
const exportLogs = mkdtempSync3(join21(tmpdir5(), "gdharness-export-"));
|
|
38856
38895
|
const exportArgs = [
|
|
38857
38896
|
"--headless",
|
|
38858
38897
|
"--log-file",
|
|
38859
|
-
|
|
38898
|
+
join21(exportLogs, "engine.log"),
|
|
38860
38899
|
"--path",
|
|
38861
38900
|
project.value.path,
|
|
38862
38901
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -38914,7 +38953,7 @@ class GodotServer {
|
|
|
38914
38953
|
return contained.response;
|
|
38915
38954
|
}
|
|
38916
38955
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
38917
|
-
if (!existsSync14(
|
|
38956
|
+
if (!existsSync14(join21(project.value.path, runner))) {
|
|
38918
38957
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
38919
38958
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
38920
38959
|
]);
|
|
@@ -38923,7 +38962,7 @@ class GodotServer {
|
|
|
38923
38962
|
if (!engine.ok) {
|
|
38924
38963
|
return engine.response;
|
|
38925
38964
|
}
|
|
38926
|
-
const classes = await this.
|
|
38965
|
+
const classes = await this.rebuildClassCache(project.value.path);
|
|
38927
38966
|
if (!classes.ok) {
|
|
38928
38967
|
return this.answer(classes);
|
|
38929
38968
|
}
|
|
@@ -38949,7 +38988,7 @@ class GodotServer {
|
|
|
38949
38988
|
];
|
|
38950
38989
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
38951
38990
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
38952
|
-
const userData = mkdtempSync3(
|
|
38991
|
+
const userData = mkdtempSync3(join21(tmpdir5(), "gdharness-tests-"));
|
|
38953
38992
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
38954
38993
|
const hung = await new Promise((resolve) => {
|
|
38955
38994
|
const timer = setTimeout(() => {
|
|
@@ -38965,14 +39004,14 @@ class GodotServer {
|
|
|
38965
39004
|
resolve(false);
|
|
38966
39005
|
});
|
|
38967
39006
|
});
|
|
38968
|
-
const reportsDir =
|
|
39007
|
+
const reportsDir = join21(project.value.path, ".godot", ...ours.split("/"));
|
|
38969
39008
|
let report = null;
|
|
38970
39009
|
let reportProblem = null;
|
|
38971
39010
|
try {
|
|
38972
39011
|
const written = existsSync14(reportsDir) ? readdirSync7(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
38973
39012
|
const newest = written.at(-1);
|
|
38974
39013
|
if (newest !== undefined) {
|
|
38975
|
-
report = parseJUnit(
|
|
39014
|
+
report = parseJUnit(readFileSync19(join21(reportsDir, newest, "results.xml"), "utf8"));
|
|
38976
39015
|
}
|
|
38977
39016
|
} catch (error) {
|
|
38978
39017
|
reportProblem = errorMessage(error);
|
|
@@ -39112,7 +39151,7 @@ class GodotServer {
|
|
|
39112
39151
|
return null;
|
|
39113
39152
|
}
|
|
39114
39153
|
try {
|
|
39115
|
-
return
|
|
39154
|
+
return readFileSync19(contained.absolutePath, "utf8");
|
|
39116
39155
|
} catch {
|
|
39117
39156
|
return null;
|
|
39118
39157
|
}
|
|
@@ -39140,7 +39179,8 @@ class GodotServer {
|
|
|
39140
39179
|
}
|
|
39141
39180
|
const stale = unloaded.filter((type) => type.inTheClassCache);
|
|
39142
39181
|
if (stale.length > 0) {
|
|
39143
|
-
|
|
39182
|
+
const remedy = this.editorWritesAShortList(projectPath) ? "editor_rescan will not clear it here: this editor wrote the class cache from a list shorter than the file on its last scan and writes the same list on every scan, so editor_launch restart is what loads it." : "editor_rescan clears it, measured against a real editor with waiting the same length ruled out; editor_launch restart also does, and costs more.";
|
|
39183
|
+
notes.push(`${stale.map((type) => type.type).join(", ")} ${stale.length === 1 ? "is" : "are"} in the class cache and still unresolved here, which is the editor's loaded list being behind rather than anything on disk. A game launched now reads the cache and resolves ${stale.length === 1 ? "it" : "them"}, so the run and the diagnostics disagree. ${remedy}`);
|
|
39144
39184
|
}
|
|
39145
39185
|
return {
|
|
39146
39186
|
...contradicted.length === 0 ? {} : { contradictedByTheFile: contradicted },
|
|
@@ -39148,13 +39188,24 @@ class GodotServer {
|
|
|
39148
39188
|
...notes.length === 0 ? {} : { staleAnalysis: notes.join(" ") }
|
|
39149
39189
|
};
|
|
39150
39190
|
}
|
|
39191
|
+
editorWritesAShortList(projectPath) {
|
|
39192
|
+
const recorded = readClassNote(projectPath)?.shortListEditorPid;
|
|
39193
|
+
return recorded !== undefined && recorded === (this.godotBridge.getStatus().editorPid ?? null);
|
|
39194
|
+
}
|
|
39195
|
+
noteAShortListEditor(projectPath) {
|
|
39196
|
+
const note = readClassNote(projectPath);
|
|
39197
|
+
writeClassNote(projectPath, {
|
|
39198
|
+
rebuiltWith: note?.rebuiltWith ?? [],
|
|
39199
|
+
shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null
|
|
39200
|
+
});
|
|
39201
|
+
}
|
|
39151
39202
|
sourceOfClass(projectPath, resourcePath) {
|
|
39152
39203
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
39153
39204
|
if (!contained.ok || !existsSync14(contained.absolutePath)) {
|
|
39154
39205
|
return null;
|
|
39155
39206
|
}
|
|
39156
39207
|
try {
|
|
39157
|
-
return
|
|
39208
|
+
return readFileSync19(contained.absolutePath, "utf8");
|
|
39158
39209
|
} catch {
|
|
39159
39210
|
return null;
|
|
39160
39211
|
}
|
|
@@ -39383,7 +39434,7 @@ class GodotServer {
|
|
|
39383
39434
|
return null;
|
|
39384
39435
|
}
|
|
39385
39436
|
try {
|
|
39386
|
-
return settingKeys(
|
|
39437
|
+
return settingKeys(readFileSync19(join21(projectPath, "project.godot"), "utf8"));
|
|
39387
39438
|
} catch {
|
|
39388
39439
|
return null;
|
|
39389
39440
|
}
|
|
@@ -39625,7 +39676,7 @@ class GodotServer {
|
|
|
39625
39676
|
}
|
|
39626
39677
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
39627
39678
|
const budgetMs = budget.ms;
|
|
39628
|
-
if (!existsSync14(
|
|
39679
|
+
if (!existsSync14(join21(projectPath, RUNTIME_AUTOLOAD2.path))) {
|
|
39629
39680
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
39630
39681
|
}
|
|
39631
39682
|
let asking = null;
|
|
@@ -40497,6 +40548,9 @@ class GodotServer {
|
|
|
40497
40548
|
}
|
|
40498
40549
|
const after = projectPath === "" || busy ? null : cachedClasses(projectPath);
|
|
40499
40550
|
const lost = before === null || after === null ? [] : [...before.keys()].filter((name) => !after.has(name));
|
|
40551
|
+
if (lost.length > 0) {
|
|
40552
|
+
this.noteAShortListEditor(projectPath);
|
|
40553
|
+
}
|
|
40500
40554
|
const atMissingPaths = after === null ? [] : cachedAtMissingPaths(after, projectPath);
|
|
40501
40555
|
const restored = lost.length > 0 || atMissingPaths.length > 0 ? await this.rebuildCacheAfterLoss(projectPath, lost) : [];
|
|
40502
40556
|
const rebuilt = atMissingPaths.length > 0 ? cachedClasses(projectPath) : null;
|
|
@@ -40552,7 +40606,7 @@ class GodotServer {
|
|
|
40552
40606
|
if (projectPath === "") {
|
|
40553
40607
|
return [];
|
|
40554
40608
|
}
|
|
40555
|
-
const rebuilt = await this.
|
|
40609
|
+
const rebuilt = await this.rebuildClassCache(projectPath);
|
|
40556
40610
|
if (!rebuilt.ok) {
|
|
40557
40611
|
this.logDebug(`Could not rebuild the class cache after a scan: ${rebuilt.message}`);
|
|
40558
40612
|
return [];
|
|
@@ -40560,6 +40614,36 @@ class GodotServer {
|
|
|
40560
40614
|
const now = cachedClasses(projectPath);
|
|
40561
40615
|
return now === null ? [] : lost.filter((name) => now.has(name));
|
|
40562
40616
|
}
|
|
40617
|
+
async rebuildClassCache(projectPath) {
|
|
40618
|
+
const before = cachedClasses(projectPath);
|
|
40619
|
+
const noted = readClassNote(projectPath);
|
|
40620
|
+
const rebuilt = await this.operation("refresh_class_cache", {}, projectPath);
|
|
40621
|
+
if (!rebuilt.ok) {
|
|
40622
|
+
return rebuilt;
|
|
40623
|
+
}
|
|
40624
|
+
const after = cachedClasses(projectPath);
|
|
40625
|
+
if (after === null) {
|
|
40626
|
+
return rebuilt;
|
|
40627
|
+
}
|
|
40628
|
+
const lost = noted === null || before === null ? [] : noted.rebuiltWith.filter((name) => !before.has(name) && after.has(name)).sort();
|
|
40629
|
+
const seenLosing = lost.length > 0 && this.godotBridge.isConnected();
|
|
40630
|
+
writeClassNote(projectPath, {
|
|
40631
|
+
rebuiltWith: [...after.keys()].sort(),
|
|
40632
|
+
...seenLosing ? { shortListEditorPid: this.godotBridge.getStatus().editorPid ?? null } : noted?.shortListEditorPid === undefined ? {} : { shortListEditorPid: noted.shortListEditorPid }
|
|
40633
|
+
});
|
|
40634
|
+
if (!seenLosing) {
|
|
40635
|
+
return rebuilt;
|
|
40636
|
+
}
|
|
40637
|
+
const them = lost.length === 1 ? "it" : "them";
|
|
40638
|
+
return {
|
|
40639
|
+
...rebuilt,
|
|
40640
|
+
payload: {
|
|
40641
|
+
...rebuilt.payload,
|
|
40642
|
+
lostSinceLastRebuild: lost,
|
|
40643
|
+
note: `The cache held ${lost.join(", ")} after the last rebuild and not when this one began, so the editor holding this project wrote it without ${them} in between: it holds a list shorter than the files and writes that list on every save and scan, and each rebuild puts ${them} back. editor_launch restart is what ends it.`
|
|
40644
|
+
}
|
|
40645
|
+
};
|
|
40646
|
+
}
|
|
40563
40647
|
async alsoSayWhatTheEditorCannotSee(answered, args) {
|
|
40564
40648
|
const first = answered.content[0];
|
|
40565
40649
|
if (answered.isError || first?.type !== "text" || typeof first.text !== "string") {
|
|
@@ -40570,15 +40654,18 @@ class GodotServer {
|
|
|
40570
40654
|
if (checked.unseen.length === 0 && stillHeld.length === 0 && checked.unchecked === undefined) {
|
|
40571
40655
|
return answered;
|
|
40572
40656
|
}
|
|
40573
|
-
const
|
|
40657
|
+
const payload = asParams(JSON.parse(first.text));
|
|
40658
|
+
const said = readString(payload, "note");
|
|
40659
|
+
const notes = said === undefined ? [] : [said];
|
|
40574
40660
|
if (checked.unseen.length > 0) {
|
|
40575
|
-
|
|
40661
|
+
const projectPath = typeof args["projectPath"] === "string" ? args["projectPath"] : "";
|
|
40662
|
+
notes.push(projectPath !== "" && this.editorWritesAShortList(projectPath) ? "The cache on disk is right now, and the editor holding this project is still not resolving these: rewriting the file does not reach the list it already loaded. editor_rescan will not reach it either on this editor, whose last scan wrote the cache from a list shorter than the file and would again; editor_launch restart is what loads them." : "The cache on disk is right now, and the editor holding this project is still not resolving these: rewriting the file does not reach the list it already loaded. editor_rescan does, on its own and with no change to the declaring script; editor_launch restart also does, and costs more.");
|
|
40576
40663
|
}
|
|
40577
40664
|
if (stillHeld.length > 0) {
|
|
40578
40665
|
notes.push(`The editor is still holding ${stillHeld.join(", ")}, which this project no longer declares, and it will write that list back over the cache this call just corrected. The entry returns pointing at a script that is gone, and the next engine to walk get_global_class_list() dies on "File not found". A rescan does not clear this one: it picks up a class that has appeared and does not drop one that has gone. editor_launch restart does.`);
|
|
40579
40666
|
}
|
|
40580
40667
|
return this.jsonTextResponse({
|
|
40581
|
-
...
|
|
40668
|
+
...payload,
|
|
40582
40669
|
unseenByEditor: checked.unseen.length > 0 ? checked.unseen : undefined,
|
|
40583
40670
|
stillHeldByEditor: stillHeld.length > 0 ? stillHeld : undefined,
|
|
40584
40671
|
classesUnchecked: checked.unchecked,
|
|
@@ -40703,8 +40790,8 @@ class GodotServer {
|
|
|
40703
40790
|
]);
|
|
40704
40791
|
}
|
|
40705
40792
|
const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
|
|
40706
|
-
const screenshotDir = expectsScreenshot ? mkdtempSync3(
|
|
40707
|
-
const screenshotPath = screenshotDir ?
|
|
40793
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync3(join21(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
40794
|
+
const screenshotPath = screenshotDir ? join21(screenshotDir, "capture.png") : null;
|
|
40708
40795
|
try {
|
|
40709
40796
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
40710
40797
|
if (!reply.ok) {
|
|
@@ -40728,7 +40815,7 @@ class GodotServer {
|
|
|
40728
40815
|
type: "text",
|
|
40729
40816
|
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}`
|
|
40730
40817
|
},
|
|
40731
|
-
{ type: "image", data:
|
|
40818
|
+
{ type: "image", data: readFileSync19(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
40732
40819
|
]
|
|
40733
40820
|
};
|
|
40734
40821
|
} finally {
|
|
@@ -40804,6 +40891,7 @@ var init_server3 = __esm(() => {
|
|
|
40804
40891
|
init_breakpoint_note();
|
|
40805
40892
|
init_bridge_announce();
|
|
40806
40893
|
init_class_cache();
|
|
40894
|
+
init_class_note();
|
|
40807
40895
|
init_config_pin();
|
|
40808
40896
|
init_dap_client();
|
|
40809
40897
|
init_errors();
|
|
@@ -40830,7 +40918,7 @@ var init_server3 = __esm(() => {
|
|
|
40830
40918
|
init_tool_definitions();
|
|
40831
40919
|
init_update_check();
|
|
40832
40920
|
run7 = promisify7(execFile7);
|
|
40833
|
-
__dirname2 =
|
|
40921
|
+
__dirname2 = dirname14(fileURLToPath4(import.meta.url));
|
|
40834
40922
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
40835
40923
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
40836
40924
|
VALUE_NAMES = {
|
|
@@ -40871,7 +40959,7 @@ var init_server3 = __esm(() => {
|
|
|
40871
40959
|
|
|
40872
40960
|
// src/cli.ts
|
|
40873
40961
|
import { existsSync as existsSync15 } from "fs";
|
|
40874
|
-
import { join as
|
|
40962
|
+
import { join as join22, resolve as resolve5 } from "path";
|
|
40875
40963
|
|
|
40876
40964
|
// src/errors.ts
|
|
40877
40965
|
class Refusal extends Error {
|
|
@@ -42576,7 +42664,7 @@ class UsageError extends Refusal {
|
|
|
42576
42664
|
function projectArgument(at) {
|
|
42577
42665
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
42578
42666
|
const projectPath = resolve5(named ?? ".");
|
|
42579
|
-
if (!existsSync15(
|
|
42667
|
+
if (!existsSync15(join22(projectPath, "project.godot"))) {
|
|
42580
42668
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
42581
42669
|
}
|
|
42582
42670
|
return projectPath;
|
|
@@ -42615,7 +42703,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
42615
42703
|
return;
|
|
42616
42704
|
}
|
|
42617
42705
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
42618
|
-
if (!existsSync15(
|
|
42706
|
+
if (!existsSync15(join22(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
42619
42707
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
42620
42708
|
}
|
|
42621
42709
|
}
|