gdharness 1.3.61 → 1.3.62
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/README.md +2 -2
- package/build/cli.js +131 -103
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,8 +48,8 @@ editor_output { "projectPath": "C:/games/shop" } // errors and warni
|
|
|
48
48
|
The open editor plays the game, so you can set a breakpoint and read the variables in scope when it
|
|
49
49
|
stops.
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
says which state it is in and what would change it.
|
|
51
|
+
Writes answer with what the engine holds afterwards, never an echo of the request. A tool that
|
|
52
|
+
cannot answer says which state it is in and what would change it.
|
|
53
53
|
|
|
54
54
|
## At a glance
|
|
55
55
|
|
package/build/cli.js
CHANGED
|
@@ -17264,6 +17264,52 @@ var init_lsp_client = __esm(() => {
|
|
|
17264
17264
|
];
|
|
17265
17265
|
});
|
|
17266
17266
|
|
|
17267
|
+
// src/nearest.ts
|
|
17268
|
+
function similarity(one, other) {
|
|
17269
|
+
if (one === other) {
|
|
17270
|
+
return 1;
|
|
17271
|
+
}
|
|
17272
|
+
if (one.length < 2 || other.length < 2) {
|
|
17273
|
+
return 0;
|
|
17274
|
+
}
|
|
17275
|
+
const pairs = (text) => Array.from({ length: text.length - 1 }, (_, at) => text.slice(at, at + 2));
|
|
17276
|
+
const ones = pairs(one);
|
|
17277
|
+
const others = pairs(other);
|
|
17278
|
+
const shared = ones.filter((pair) => others.includes(pair)).length;
|
|
17279
|
+
return 2 * shared / (ones.length + others.length);
|
|
17280
|
+
}
|
|
17281
|
+
function withNearestFiles(said, wanted, files, beside) {
|
|
17282
|
+
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
17283
|
+
const named = said.split(/[\s,;]+/).map((word) => word.replace(/[.:]+$/, ""));
|
|
17284
|
+
const namesakes = beside.filter((file) => file !== wanted && stem(file) === stem(wanted) && !named.includes(file)).toSorted();
|
|
17285
|
+
if (namesakes.length > 0) {
|
|
17286
|
+
const alone = namesakes.length === 1;
|
|
17287
|
+
return `${said}. The ${alone ? "file" : "files"} of that name beside it ${alone ? "is" : "are"} ${namesakes.join(", ")}.`;
|
|
17288
|
+
}
|
|
17289
|
+
const extension = /\.([^./]+)$/.exec(wanted)?.[1];
|
|
17290
|
+
if (extension === undefined) {
|
|
17291
|
+
return said;
|
|
17292
|
+
}
|
|
17293
|
+
if (files.length === 0) {
|
|
17294
|
+
return `${said}. The project holds no .${extension} file.`;
|
|
17295
|
+
}
|
|
17296
|
+
const close = nearest(stem(wanted), files.map((file) => stem(file)));
|
|
17297
|
+
const offered = files.filter((file) => close.includes(stem(file)));
|
|
17298
|
+
if (offered.length > 0) {
|
|
17299
|
+
return `${said}. The nearest .${extension} ${offered.length === 1 ? "file is" : "files are"} ${offered.join(", ")}.`;
|
|
17300
|
+
}
|
|
17301
|
+
if (files.length <= LISTED_FILES) {
|
|
17302
|
+
return `${said}. The project's .${extension} files are ${files.join(", ")}.`;
|
|
17303
|
+
}
|
|
17304
|
+
return `${said}. None of the project's ${files.length} .${extension} files is named like it.`;
|
|
17305
|
+
}
|
|
17306
|
+
function nearest(word, names) {
|
|
17307
|
+
const wanted = word.toLowerCase();
|
|
17308
|
+
const score = (name) => similarity(name.toLowerCase(), wanted);
|
|
17309
|
+
return names.filter((name) => score(name) >= 0.5).toSorted((one, other) => score(other) - score(one)).slice(0, 3);
|
|
17310
|
+
}
|
|
17311
|
+
var LISTED_FILES = 12;
|
|
17312
|
+
|
|
17267
17313
|
// src/resources.ts
|
|
17268
17314
|
import { readFileSync as readFileSync21, realpathSync as realpathSync4 } from "node:fs";
|
|
17269
17315
|
import { extname as extname4, resolve as resolve10 } from "node:path";
|
|
@@ -18093,7 +18139,7 @@ function annotationsOf(spec) {
|
|
|
18093
18139
|
openWorldHint: false
|
|
18094
18140
|
};
|
|
18095
18141
|
}
|
|
18096
|
-
var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "
|
|
18142
|
+
var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "The call goes through the editor when one serves the project. With none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. Each file the written one refers to is named by its uid, as the editor names it, except a file the project's uid cache has no uid for yet (one made since the project was last scanned), which is named by path and listed under namedByPath with the call that scans it. A running editor of the project that has not yet reached this server is waited for.", ALSO_SAVED = "A scene is saved as the engine holds it once the file has loaded, and one property can set others as it loads, as anchors_preset gives a centred Control both its grow directions, so a scene not last saved by an editor can gain, change or lose lines the call did not ask for, on any node the file held, the one it names among them: alsoSaved lists each as {node, property, was, saved}, was left out where the file set nothing and saved where it now sets nothing. What a set itself moved on its node is under alsoChanged rather than here, and neither a node the call adds nor the format lines (header uid, load_steps, unique_id) are listed. The scene loads as it did. Only its text changed.", IMPORTED_FIRST = "importedFirst names the files whose import was behind them on disk, such as a picture changed since it was imported or one never imported, which were imported before the run through project_import reimport, so the run reads them as they are now. importedFirst is left out when every import was current. notImportedFirst lists, as project_import reimport does, any it could not import, and importNote says that the run reads those as they were. The import gives every script without a .uid sidecar one, as the editor's scan does, writing it beside the script in the project, and uidsWrittenFirst names those scripts. The files inside an addon are counted per addon rather than named, under importedInAddons and uidsWrittenInAddons. importTookMs is how long the import took. That time is outside the run's own time limit, so a call that imported can take that much longer than the limit it was given.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', EXPORT_PLATFORMS, RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR2, TOOL_SPECS, SPECS_BY_NAME;
|
|
18097
18143
|
var init_tool_definitions = __esm(() => {
|
|
18098
18144
|
init_launch();
|
|
18099
18145
|
PROJECT_INFO_SECTIONS = [
|
|
@@ -18348,7 +18394,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18348
18394
|
name: "project_settings",
|
|
18349
18395
|
title: "Project settings",
|
|
18350
18396
|
description: "Reads or writes project.godot (settings, autoloads, the main scene, input actions and plugins) and the audio bus layout, default_bus_layout.tres unless audio/buses/default_bus_layout names another. project_info include lists the autoloads, input actions and audio buses as the project holds them. A write with an editor open on the project is handed to the editor, which takes the settings the write names from the file (named under editorAdopted), even one the file already held, or reloads the bus layout, so it does not save the old values back over them. An enabled plugin's code still loads on the editor's next start, and enable_plugin notes a script it would not load. add_autoload refuses a name the editor's Autoload tab refuses: not an identifier, a keyword, a built-in type or an engine class. add_input_action on an existing action, a ui_ one included, replaces its events, and its deadzone when one is given. A set of the value the setting already holds, in the same type, writes nothing. unset takes a setting's line out of project.godot, and on one the file does not set it writes nothing. Every write changes only the lines of the settings it names and keeps every other line as the file had it, in its place. A setting res://override.cfg or the project_settings_override file sets is one a run lays over project.godot and the editor does not.",
|
|
18351
|
-
answer: "get answers exists, true for any setting the engine knows, at_default, true when the value is the engine's own default and so has no line in project.godot, and overrides, the value each feature's override holds. Each setting under prefix carries at_default too. at_default is absent for a setting with no engine default, such as an input action the project added. An override answers override_of, its base setting, and one project.godot does not set answers exists false with holds, the base setting's value. A setting an override file sets is answered with the project's value and overridden_in, the file and the value a run reads. " + "set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. It adds overrides with overridesNote when a feature's override still replaces the setting, and overriddenNote when an override file does. A set that writes nothing answers unchanged true and saved false. " + "unset answers removed, old_value and default_value, the value that holds now. default_value is null for a setting the engine does not declare.
|
|
18397
|
+
answer: "get answers exists, true for any setting the engine knows, at_default, true when the value is the engine's own default and so has no line in project.godot, and overrides, the value each feature's override holds. Each setting under prefix carries at_default too. at_default is absent for a setting with no engine default, such as an input action the project added. An override answers override_of, its base setting, and one project.godot does not set answers exists false with holds, the base setting's value. A setting an override file sets is answered with the project's value and overridden_in, the file and the value a run reads. " + "set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. It adds overrides with overridesNote when a feature's override still replaces the setting, and overriddenNote when an override file does. A set that writes nothing answers unchanged true and saved false. " + "unset answers removed, old_value and default_value, the value that holds now. default_value is null for a setting the engine does not declare. unset of an override, such as name.feature, also answers override_of and holds, the base setting's value that builds with the feature now read. On a setting the file does not set, unset answers removed false, its note naming the nearest settings when the engine does not declare it either. " + "add_input_action on an existing action answers was_new false with old_events and old_deadzone. " + "editorNote, on any write, is an editor that could not take the write, or one on another release's addon that was not asked. That editor still holds the old values until editor_launch restart.",
|
|
18352
18398
|
parameters: {
|
|
18353
18399
|
projectPath: PROJECT_PATH,
|
|
18354
18400
|
setting: {
|
|
@@ -18723,7 +18769,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18723
18769
|
title: "Run tests",
|
|
18724
18770
|
effect: "additive",
|
|
18725
18771
|
changes: "Writes under `.godot` and adds `.uid` files. Changes no script or scene.",
|
|
18726
|
-
description: "Runs the project's gdUnit4 suites in one engine and answers with every case that did not pass, with where it is and what its assertion said.
|
|
18772
|
+
description: "Runs the project's gdUnit4 suites in one engine and answers with every case that did not pass, with where it is and what its assertion said. The class list is rebuilt first, so a suite written a moment ago is found. What changed on disk since it was imported is then imported before any suite runs. On Windows and Linux the run gets a user:// of its own, away from a player's saves. On macOS the engine reads user:// off HOME, so it writes where the player keeps theirs. The run is headless unless a suite in it needs a window as gdharness test reads it, and then has one, on a desktop of its own on Windows. A run that found nothing to run is never called a pass, and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs, and an autoload that does not compile stops the run at once. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. A run whose every case passed is still not called passed when it printed a script error no case failed on. gdUnit4 fails a case only on the script errors its logger sees, and it cannot see a stack overflow. Needs gdUnit4 under addons/gdUnit4.",
|
|
18727
18773
|
answer: [
|
|
18728
18774
|
"passed is the verdict to read.",
|
|
18729
18775
|
"",
|
|
@@ -18775,7 +18821,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18775
18821
|
},
|
|
18776
18822
|
failFast: {
|
|
18777
18823
|
type: "boolean",
|
|
18778
|
-
description: "Stop each suite at its first failing case. Default false: every case runs. With it on, the counts
|
|
18824
|
+
description: "Stop each suite at its first failing case. Default false: every case runs. With it on, the counts cover only what ran. notRun counts the cases left. Each run after a fix then finds the next failure. That comes from failFast, not a flaky suite."
|
|
18779
18825
|
},
|
|
18780
18826
|
timeoutMs: {
|
|
18781
18827
|
type: "integer",
|
|
@@ -19507,7 +19553,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
19507
19553
|
savesIn: {
|
|
19508
19554
|
type: "string",
|
|
19509
19555
|
ops: ["start", "check"],
|
|
19510
|
-
description: "An absolute directory for this run's user://, so a game that saves writes nowhere near the copy somebody plays: opening a save writes the one being left, and a game that autosaves writes on its own. Godot takes no flag for it, so this is the environment, and the run is started by this server rather than played by the editor, which cannot be given one. It does not have to exist: the engine makes it
|
|
19556
|
+
description: "An absolute directory for this run's user://, so a game that saves writes nowhere near the copy somebody plays: opening a save writes the one being left, and a game that autosaves writes on its own. Godot takes no flag for it, so this is the environment, and the run is started by this server rather than played by the editor, which cannot be given one. It does not have to exist: the engine makes it. Under it the engine makes Godot/app_userdata/<project>, or the custom folder's name alone when the project sets application/config/use_custom_user_dir. Whatever the run, runtime.userDir in the answer is the directory user:// is, as the game's engine resolved it. On macOS the engine reads user:// off HOME and ignores it, also measured, and the answer says so under savesNote rather than leaving the caller to find out from the player's save list."
|
|
19511
19557
|
},
|
|
19512
19558
|
env: {
|
|
19513
19559
|
type: "object",
|
|
@@ -19610,7 +19656,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
19610
19656
|
{
|
|
19611
19657
|
name: "editor_output",
|
|
19612
19658
|
title: "Game and editor output",
|
|
19613
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports. A played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. A run answers with counts and a verdict: clean is true when no error was reported, and warnings, counted beside it, do not make it false. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one reported a game as playing fifteen seconds after its process was ended from outside. editor_status
|
|
19659
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports. A played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. A run answers with counts and a verdict: clean is true when no error was reported, and warnings, counted beside it, do not make it false. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one reported a game as playing fifteen seconds after its process was ended from outside. editor_status does not list that game under runtimes, which holds only games that announced. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, where the output worth reading usually is, since the class cache, the plugins and the language server speak before any harness connects. It answers counts with no clean, since an editor neither passes nor fails, the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going. For a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
|
|
19614
19660
|
answer: "An entry with lines under its headline has them as detail, and, when one of them names a project script, script, line and function say where: the at: line when it names one, otherwise the innermost frame of the backtrace that does. A push_error or push_warning has the engine's own push_error as its at: line, so its script and line are the game's call that raised it. Each entry's source says where it was read. transcript is a line of the run's transcript file, which holds both its streams and is how every run with one is read. stdout or stderr is a line of that stream of a run with no transcript, such as an editor-played run picked up after a reconnect whose first server left no note. debugger is what the editor's debug adapter reported and the game never printed, such as the script error a game stopped on. A printerr line is info: it prints to stderr, and the engine reports no error for it. For op editor with no editor connected, connected is false, editorRunning says whether the editor whose console it is still runs, and editorNote whether this server closed it or it went without a close, as a crash does. The next editor gdharness opens on the project starts a new console. With none connected and none ever opened here on the project, there is no console, and the refusal says so.",
|
|
19615
19661
|
parameters: {
|
|
19616
19662
|
projectPath: {
|
|
@@ -19677,7 +19723,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
19677
19723
|
title: "Rescan the project",
|
|
19678
19724
|
effect: "additive",
|
|
19679
19725
|
changes: "Writes under `.godot` and adds `.uid` files. Changes no script or scene.",
|
|
19680
|
-
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it. The scan is a change-detecting walk rather than an unconditional reparse
|
|
19726
|
+
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it. The scan the editor runs on its own, when its window takes the focus or files change under it, is a change-detecting walk rather than an unconditional reparse. A file another engine has already imported reads as settled there and the walk does not look inside it. Its class_name then stays out of the list the editor resolves against, however often the editor scans on its own. Any class in that state is named under unseenByEditor, which no check on disk can see. Calling it again is the cure, with no change to the declaring script. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate. Before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. A script that fails to compile is tried again once the scripts it is compiled against that the editor holds behind their files are reloaded, which are named under reloadedFirst. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure. What the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped. The editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
|
|
19681
19727
|
answer: "waitedMs is the whole call, and waitedForEditorScanMs, given only when a scan or an import was already running, the part spent waiting it out. waitedForEditorAnswerMs, given when an idle editor took a second or more to say so, is how long that took: such an editor is busy on the thread that answers with an earlier request rather than scanning, so the time is added to timeoutMs rather than taken from it, and the note says so. stillWorking is true when a scan was still running at the end of the wait. The scan the call started is looked at once even when the wait has run out, so stillWorking is never said of a scan nothing looked at.",
|
|
19682
19728
|
parameters: {
|
|
19683
19729
|
projectPath: PROJECT_PATH,
|
|
@@ -19787,7 +19833,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
19787
19833
|
type: "integer",
|
|
19788
19834
|
minimum: 0,
|
|
19789
19835
|
ops: ["tree"],
|
|
19790
|
-
description: "tree: levels to descend, 0 for the node alone. Default 3."
|
|
19836
|
+
description: "tree: levels to descend, 0 for the node alone. Default 3. A node at the last level that has children answers children_not_shown with their count instead of children."
|
|
19791
19837
|
},
|
|
19792
19838
|
includeProperties: {
|
|
19793
19839
|
type: "boolean",
|
|
@@ -20378,7 +20424,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
20378
20424
|
});
|
|
20379
20425
|
|
|
20380
20426
|
// src/headless-operations.ts
|
|
20381
|
-
var HEADLESS_OPERATIONS, ENGINE_PASSES, EDITOR_READS;
|
|
20427
|
+
var HEADLESS_OPERATIONS, ENGINE_PASSES, DEPENDENCY_OPERATIONS, HEADLESS_TOOLS, EDITOR_READS;
|
|
20382
20428
|
var init_headless_operations = __esm(() => {
|
|
20383
20429
|
HEADLESS_OPERATIONS = dictionary({
|
|
20384
20430
|
project_settings: dictionary({
|
|
@@ -20418,6 +20464,13 @@ var init_headless_operations = __esm(() => {
|
|
|
20418
20464
|
ENGINE_PASSES = dictionary({
|
|
20419
20465
|
project_import: dictionary({ refresh_uids: "import", reimport: "import" })
|
|
20420
20466
|
});
|
|
20467
|
+
DEPENDENCY_OPERATIONS = {
|
|
20468
|
+
forward: "get_dependencies",
|
|
20469
|
+
reverse: "find_resource_usages"
|
|
20470
|
+
};
|
|
20471
|
+
HEADLESS_TOOLS = dictionary({
|
|
20472
|
+
project_dependencies: Object.values(DEPENDENCY_OPERATIONS)
|
|
20473
|
+
});
|
|
20421
20474
|
EDITOR_READS = dictionary({
|
|
20422
20475
|
project_settings: dictionary({ get: "get_project_setting" })
|
|
20423
20476
|
});
|
|
@@ -20446,13 +20499,15 @@ function headlessSection() {
|
|
|
20446
20499
|
everyOp.set(tool, [...everyOp.get(tool) ?? [], ...Object.keys(ops)]);
|
|
20447
20500
|
}
|
|
20448
20501
|
}
|
|
20449
|
-
const byTool = [
|
|
20502
|
+
const byTool = [
|
|
20503
|
+
...[...everyOp].map(([tool, ops]) => `\`${tool}\` (${ops.join(", ")})`),
|
|
20504
|
+
...Object.keys(HEADLESS_TOOLS).map((tool) => `\`${tool}\``)
|
|
20505
|
+
].toSorted();
|
|
20450
20506
|
return [
|
|
20451
20507
|
"## Calls a CI job can make",
|
|
20452
20508
|
"",
|
|
20453
20509
|
"These answer from a short-lived headless engine: no editor window, no running game, and no",
|
|
20454
|
-
"process or port outliving the answer.
|
|
20455
|
-
"all, because every other tool needs an editor somebody has open or a game somebody is running.",
|
|
20510
|
+
"process or port outliving the answer.",
|
|
20456
20511
|
"",
|
|
20457
20512
|
...byTool.map((line) => `- ${line}`),
|
|
20458
20513
|
"",
|
|
@@ -20461,11 +20516,14 @@ function headlessSection() {
|
|
|
20461
20516
|
"write under `.godot`, and `project_import refresh_uids` writes a `.uid` beside each script that",
|
|
20462
20517
|
"had none. A CI job checks with the reading ops. Each op's own summary below says which is which.",
|
|
20463
20518
|
"",
|
|
20464
|
-
"`scene_create`, `scene_node`, `scene_signal` and `resource_edit`
|
|
20465
|
-
"serves the project and through a headless engine when none does
|
|
20466
|
-
"
|
|
20467
|
-
"
|
|
20468
|
-
"
|
|
20519
|
+
"`scene_create`, `scene_node`, `scene_signal`, `scene_animation`, `scene_tree` and `resource_edit`",
|
|
20520
|
+
"go through the editor when one serves the project and through a headless engine when none does.",
|
|
20521
|
+
"`project_info`, `project_search`, `project_test` and `script_diagnostics` need no editor either.",
|
|
20522
|
+
"A CI job can call all of them, and checks with the reads among them. `editor_run check` boots the",
|
|
20523
|
+
"project in a headless engine of its own, so a CI job can call it too. The other `editor_*`",
|
|
20524
|
+
"calls, and the `debug_*` and `runtime_*` ones, act on an editor or a running game.",
|
|
20525
|
+
"`editor_run start` starts a game with no editor open. `debug_breakpoint` holds breakpoints until",
|
|
20526
|
+
"a game runs.",
|
|
20469
20527
|
"",
|
|
20470
20528
|
'`project_settings get` with `from: "editor"` asks the open editor instead of the file and',
|
|
20471
20529
|
"starts no engine. A CI job leaves it alone, because there is no editor on a build machine and",
|
|
@@ -20506,8 +20564,9 @@ function renderToolsMarkdown() {
|
|
|
20506
20564
|
`${TOOL_SPECS.length} tools, named \`domain_verb\`. A tool that does several related things takes an`,
|
|
20507
20565
|
"`op`. An unknown op or argument is refused with the valid set listed. Generated from the server.",
|
|
20508
20566
|
"",
|
|
20509
|
-
`${projectPathSentence()}
|
|
20510
|
-
"
|
|
20567
|
+
`${projectPathSentence()}. The \`debug_*\` calls among them answer about the game this server`,
|
|
20568
|
+
"started or the editor is playing, and the `editor_*` ones about the editor on this server. A",
|
|
20569
|
+
"`runtime_*` call takes `projectPath` only to pick a game.",
|
|
20511
20570
|
"",
|
|
20512
20571
|
theGamePicked(),
|
|
20513
20572
|
"",
|
|
@@ -49918,52 +49977,6 @@ var init_lifecycle_log = __esm(() => {
|
|
|
49918
49977
|
init_server_version();
|
|
49919
49978
|
});
|
|
49920
49979
|
|
|
49921
|
-
// src/nearest.ts
|
|
49922
|
-
function similarity(one, other) {
|
|
49923
|
-
if (one === other) {
|
|
49924
|
-
return 1;
|
|
49925
|
-
}
|
|
49926
|
-
if (one.length < 2 || other.length < 2) {
|
|
49927
|
-
return 0;
|
|
49928
|
-
}
|
|
49929
|
-
const pairs = (text) => Array.from({ length: text.length - 1 }, (_, at) => text.slice(at, at + 2));
|
|
49930
|
-
const ones = pairs(one);
|
|
49931
|
-
const others = pairs(other);
|
|
49932
|
-
const shared = ones.filter((pair) => others.includes(pair)).length;
|
|
49933
|
-
return 2 * shared / (ones.length + others.length);
|
|
49934
|
-
}
|
|
49935
|
-
function withNearestFiles(said, wanted, files, beside) {
|
|
49936
|
-
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
49937
|
-
const named = said.split(/[\s,;]+/).map((word) => word.replace(/[.:]+$/, ""));
|
|
49938
|
-
const namesakes = beside.filter((file) => file !== wanted && stem(file) === stem(wanted) && !named.includes(file)).toSorted();
|
|
49939
|
-
if (namesakes.length > 0) {
|
|
49940
|
-
const alone = namesakes.length === 1;
|
|
49941
|
-
return `${said}. The ${alone ? "file" : "files"} of that name beside it ${alone ? "is" : "are"} ${namesakes.join(", ")}.`;
|
|
49942
|
-
}
|
|
49943
|
-
const extension = /\.([^./]+)$/.exec(wanted)?.[1];
|
|
49944
|
-
if (extension === undefined) {
|
|
49945
|
-
return said;
|
|
49946
|
-
}
|
|
49947
|
-
if (files.length === 0) {
|
|
49948
|
-
return `${said}. The project holds no .${extension} file.`;
|
|
49949
|
-
}
|
|
49950
|
-
const close = nearest(stem(wanted), files.map((file) => stem(file)));
|
|
49951
|
-
const offered = files.filter((file) => close.includes(stem(file)));
|
|
49952
|
-
if (offered.length > 0) {
|
|
49953
|
-
return `${said}. The nearest .${extension} ${offered.length === 1 ? "file is" : "files are"} ${offered.join(", ")}.`;
|
|
49954
|
-
}
|
|
49955
|
-
if (files.length <= LISTED_FILES) {
|
|
49956
|
-
return `${said}. The project's .${extension} files are ${files.join(", ")}.`;
|
|
49957
|
-
}
|
|
49958
|
-
return `${said}. None of the project's ${files.length} .${extension} files is named like it.`;
|
|
49959
|
-
}
|
|
49960
|
-
function nearest(word, names) {
|
|
49961
|
-
const wanted = word.toLowerCase();
|
|
49962
|
-
const score = (name) => similarity(name.toLowerCase(), wanted);
|
|
49963
|
-
return names.filter((name) => score(name) >= 0.5).toSorted((one, other) => score(other) - score(one)).slice(0, 3);
|
|
49964
|
-
}
|
|
49965
|
-
var LISTED_FILES = 12;
|
|
49966
|
-
|
|
49967
49980
|
// src/outside.ts
|
|
49968
49981
|
import process17 from "node:process";
|
|
49969
49982
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -53248,7 +53261,7 @@ class GodotServer {
|
|
|
53248
53261
|
return this.saying(answer, {
|
|
53249
53262
|
update_available: {
|
|
53250
53263
|
...notice,
|
|
53251
|
-
what_to_do: "Tell the user a newer gdharness is out, with what changed. Run the upgrade command " + "only with their agreement: a yes now, or a standing instruction of theirs, such as one " + "in their AGENTS.md or CLAUDE.md, to upgrade on every release. Without either, offer it " + "and wait, since it replaces the addons in their project. Afterwards reconnect the MCP " + "server. Restart
|
|
53264
|
+
what_to_do: "Tell the user a newer gdharness is out, with what changed. Run the upgrade command " + "only with their agreement: a yes now, or a standing instruction of theirs, such as one " + "in their AGENTS.md or CLAUDE.md, to upgrade on every release. Without either, offer it " + "and wait, since it replaces the addons in their project. Afterwards reconnect the MCP " + "server. Restart an open editor with editor_launch restart only when the upgrade output " + "asks for it."
|
|
53252
53265
|
}
|
|
53253
53266
|
});
|
|
53254
53267
|
}
|
|
@@ -53446,7 +53459,7 @@ class GodotServer {
|
|
|
53446
53459
|
case "project_search":
|
|
53447
53460
|
return this.handleSearchProject(args);
|
|
53448
53461
|
case "project_dependencies":
|
|
53449
|
-
return await this.headless(args["direction"] === "reverse" ? "
|
|
53462
|
+
return await this.headless(DEPENDENCY_OPERATIONS[args["direction"] === "reverse" ? "reverse" : "forward"], args);
|
|
53450
53463
|
case "project_export":
|
|
53451
53464
|
return await this.handleExportProject(args);
|
|
53452
53465
|
case "project_test":
|
|
@@ -53772,9 +53785,9 @@ class GodotServer {
|
|
|
53772
53785
|
...told === null ? {} : ("unreached" in told) || typeof told["refused"] === "string" ? {
|
|
53773
53786
|
runningGame: `The game this server started is running, and its debugger was not told: ${String("unreached" in told ? told.unreached : told["refused"])}. It takes the line when it is started again.`
|
|
53774
53787
|
} : {
|
|
53775
|
-
runningGame: told["taken"] === false ? `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started, and not taken by it within ${READ_MS / 1000}s: a game reads its debugger between frames and every 2048 lines of script, so one inside a long frame takes the
|
|
53788
|
+
runningGame: told["taken"] === false ? `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started, and not taken by it within ${READ_MS / 1000}s: a game reads its debugger between frames and every 2048 lines of script, so one inside a long frame takes the change once that frame ends.` : op === "set" ? `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started, and taken by it: anything it runs from now on stops there.` : `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started, and taken by it: it no longer stops on that line.`
|
|
53776
53789
|
},
|
|
53777
|
-
note: editor ? "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, handed to every game this server starts itself, and kept in the project for the server after a reconnect." : "No editor is connected, so the breakpoints under held are kept here: every game this server starts is handed them as it starts, an editor that connects is sent them before its first play, and they are kept in the project for the server after a reconnect.",
|
|
53790
|
+
note: editor ? "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, handed to every game this server starts itself, and kept in the project for the server after a reconnect." : held.length === 0 ? "No editor is connected and no breakpoint is held here, so the next game this server starts and the next editor to connect are given none." : "No editor is connected, so the breakpoints under held are kept here: every game this server starts is handed them as it starts, an editor that connects is sent them before its first play, and they are kept in the project for the server after a reconnect.",
|
|
53778
53791
|
...editor ? this.breakpointsAtRisk() : {}
|
|
53779
53792
|
});
|
|
53780
53793
|
}
|
|
@@ -56394,7 +56407,7 @@ ${error.text}`;
|
|
|
56394
56407
|
]);
|
|
56395
56408
|
}
|
|
56396
56409
|
if (!before.connected || before.editorPid === undefined) {
|
|
56397
|
-
return this.createErrorResponse(`An editor has opened the bridge and has not said who it is within ${greetingWaitMs() / 1000}s, so there is no process to wait on once it is asked to
|
|
56410
|
+
return this.createErrorResponse(`An editor has opened the bridge and has not said who it is within ${greetingWaitMs() / 1000}s, so there is no process to wait on once it is asked to quit.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the close again"]);
|
|
56398
56411
|
}
|
|
56399
56412
|
const pid = before.editorPid;
|
|
56400
56413
|
const settingsBefore = this.settingKeysOf(before.projectPath);
|
|
@@ -56713,7 +56726,7 @@ ${error.text}`;
|
|
|
56713
56726
|
return this.createErrorResponse("An exported build runs the main scene its pack holds, and takes no scene: leave scene out, or run the project without executable.");
|
|
56714
56727
|
}
|
|
56715
56728
|
if (exported !== null && readBoolean(args, "profile") === true) {
|
|
56716
|
-
return this.createErrorResponse("An exported build
|
|
56729
|
+
return this.createErrorResponse("An exported build is started without the debugger, because a release build quits on --remote-debug. A profile needs the debugger, so leave out executable to profile the project.");
|
|
56717
56730
|
}
|
|
56718
56731
|
const own = readStringArray(args, "args") ?? [];
|
|
56719
56732
|
const given = suites.value === null ? own : [...own, ...suitesSceneArgs(project.value.path, suites.value)];
|
|
@@ -58708,7 +58721,7 @@ ${error.text}`;
|
|
|
58708
58721
|
} else if (lost.length > 0) {
|
|
58709
58722
|
notes.push("The scan wrote the class cache from the list this editor is holding, which is shorter than the file was, so the cache was rebuilt from the files and these classes are back in it. Nothing was lost, but this editor is still holding the short list: do not rescan again until it has been restarted with editor_launch restart, because it will write the same list over the file each time.");
|
|
58710
58723
|
} else if (unseen.length > 0) {
|
|
58711
|
-
notes.push("The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported.
|
|
58724
|
+
notes.push("The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Call editor_rescan again on its own, which is the measured cure and needs no change to the declaring script, or restart the editor with editor_launch restart.");
|
|
58712
58725
|
}
|
|
58713
58726
|
if (dependents !== null && dependents.notReloaded.length > 0) {
|
|
58714
58727
|
notes.push(notReloadedNote(broughtIn, dependents.notReloaded));
|
|
@@ -60589,10 +60602,11 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
60589
60602
|
|
|
60590
60603
|
## Start here
|
|
60591
60604
|
|
|
60592
|
-
- ${projectPathSentence()}. Those
|
|
60593
|
-
|
|
60594
|
-
- \`runtime_*\`
|
|
60595
|
-
|
|
60605
|
+
- ${projectPathSentence()}. Those answer about the editor or game this server is connected to.
|
|
60606
|
+
There is no ambient project.
|
|
60607
|
+
- \`runtime_*\` calls pick a game by \`projectPath\`, or by \`pid\` when several run from one
|
|
60608
|
+
project, as a bench and its workers do. \`debug_*\` calls go to the game this server started or
|
|
60609
|
+
the editor is playing. \`debug_breakpoint\` also holds breakpoints when no game runs.
|
|
60596
60610
|
${theGamePicked()}
|
|
60597
60611
|
- An argument a tool does not declare is refused rather than ignored.
|
|
60598
60612
|
- \`editor_status\` says whether an editor is connected and whether its addon matches the server.
|
|
@@ -60614,16 +60628,16 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
60614
60628
|
growing, a process list or a timeout. From outside, a bench between prints looks the same as a
|
|
60615
60629
|
dead one.
|
|
60616
60630
|
- \`endedBy\` says whether gdharness ended a run. \`running\` comes from the operating system when
|
|
60617
|
-
the run has a pid: every run started
|
|
60618
|
-
|
|
60619
|
-
editor's answer and can lag by seconds. \`editor_status\`
|
|
60620
|
-
|
|
60631
|
+
the run has a pid: every run this server started itself, and every editor-played run whose game
|
|
60632
|
+
announced its runtime. An editor-played game with no runtime addon has no pid, so \`running\` is
|
|
60633
|
+
the editor's answer and can lag by seconds. \`editor_status\` does not list such a game under
|
|
60634
|
+
\`runtimes\`.
|
|
60621
60635
|
- When every tool says the project is fine and something is still wrong, read the editor's own
|
|
60622
60636
|
console with \`editor_output op: "editor"\`. It runs from the editor's first line, with repeats
|
|
60623
60637
|
grouped under \`repeated\`. It exists only for an editor gdharness opened, and one opened by hand
|
|
60624
60638
|
is refused. The line before a burst of parse errors often names the plugin behind it.
|
|
60625
60639
|
- One server, one editor. A second project needs a second harness session with its own server.
|
|
60626
|
-
|
|
60640
|
+
Never open a second editor on a project that already has one.
|
|
60627
60641
|
|
|
60628
60642
|
## Measuring a running game
|
|
60629
60643
|
|
|
@@ -60678,9 +60692,10 @@ prefixes, empty by default. A run started on a scene under one of them neither s
|
|
|
60678
60692
|
announces, nor do the engines it starts, unless it was started to be profiled from its start.
|
|
60679
60693
|
|
|
60680
60694
|
The autoload names \`res://${RUNTIME_AUTOLOAD.path}\`, the loader \`gdharness setup\` writes, which
|
|
60681
|
-
brings the runtime up only in a debug build that has the addon. A release export leaves the
|
|
60682
|
-
the loader and the addons
|
|
60683
|
-
|
|
60695
|
+
brings the runtime up only in a debug build that has the addon. A release export leaves out the
|
|
60696
|
+
entry, the loader and the runtime, editor and auto_reload addons. A debug export keeps them. The
|
|
60697
|
+
loader is committed with \`project.godot\`, and \`gdharness doctor\` says when it differs from the
|
|
60698
|
+
one gdharness ships.
|
|
60684
60699
|
|
|
60685
60700
|
## At a breakpoint
|
|
60686
60701
|
|
|
@@ -60699,11 +60714,11 @@ so take it off when the question it was for is answered.
|
|
|
60699
60714
|
|
|
60700
60715
|
\`scene_tree\` reads what a scene holds, rather than what the \`.tscn\` text implies, and
|
|
60701
60716
|
\`resource_edit read\` reads a resource. Writes go through \`scene_create\`, \`scene_node\`,
|
|
60702
|
-
\`scene_signal\` and
|
|
60703
|
-
one serves the project, so the files keep their formatting, and a headless engine
|
|
60704
|
-
headless engine runs the scene's scripts, so the answer puts back what they
|
|
60705
|
-
file where it cannot, such as a scene whose script does not compile. For
|
|
60706
|
-
(\`editor_launch\` with \`hidden: true\`) and ask again.
|
|
60717
|
+
\`scene_signal\`, \`scene_animation\` and every \`resource_edit\` op but \`move\`. All of them use
|
|
60718
|
+
the editor when one serves the project, so the files keep their formatting, and a headless engine
|
|
60719
|
+
otherwise. The headless engine runs the scene's scripts, so the answer puts back what they
|
|
60720
|
+
changed. It refuses a file where it cannot, such as a scene whose script does not compile. For
|
|
60721
|
+
those, open the editor (\`editor_launch\` with \`hidden: true\`) and ask again.
|
|
60707
60722
|
|
|
60708
60723
|
\`script_info\` comes from the editor's own language server, and so does
|
|
60709
60724
|
\`script_diagnostics\` when this project's editor is open. Without it a headless engine compiles the
|
|
@@ -60713,9 +60728,8 @@ scripts and reports their errors, with lines but no columns and no warnings.
|
|
|
60713
60728
|
\`project_export\`, \`project_test\`, \`script_edit\`, \`script_info structure\` and
|
|
60714
60729
|
\`editor_classes\` need nothing open: they run a short headless engine and are gone before the
|
|
60715
60730
|
answer is printed. The exception is \`script_edit rename\` of a method, variable, signal or other
|
|
60716
|
-
member, which asks the editor's language server which uses are that class's.
|
|
60717
|
-
|
|
60718
|
-
default.
|
|
60731
|
+
member, which asks the editor's language server which uses are that class's. A setting absent
|
|
60732
|
+
from \`project.godot\` comes back from \`project_settings get\` with the engine's default.
|
|
60719
60733
|
|
|
60720
60734
|
To rename a class or a member across the project, use \`script_edit rename\` rather than a text
|
|
60721
60735
|
replacement. It changes the uses the engine resolves, doc links and what scenes record, and lists
|
|
@@ -60745,10 +60759,10 @@ its own copy. So a long run is not a reason to stop editing, and the new code ar
|
|
|
60745
60759
|
|
|
60746
60760
|
A wall of "Could not find type" on the project's own classes means the editor is not holding
|
|
60747
60761
|
them. \`editor_rescan\` asks it to scan and names any class it still cannot resolve under
|
|
60748
|
-
\`unseenByEditor\`:
|
|
60749
|
-
declaration and the cache can both be right while the editor stays blind to it.
|
|
60750
|
-
\`editor_rescan\` again, which needs no change to the declaring
|
|
60751
|
-
also fixes it
|
|
60762
|
+
\`unseenByEditor\`: the editor's own scan skips a file a headless engine has already imported, so
|
|
60763
|
+
the declaration and the cache can both be right while the editor stays blind to it. For a class
|
|
60764
|
+
under \`unseenByEditor\`, run \`editor_rescan\` again, which needs no change to the declaring
|
|
60765
|
+
script. \`editor_launch restart\` also fixes it.
|
|
60752
60766
|
|
|
60753
60767
|
With no editor open, the headless engine that compiles for \`script_diagnostics\` and for
|
|
60754
60768
|
\`project_info\`'s health and validation resolves class names only from that cache file. A
|
|
@@ -60761,7 +60775,9 @@ The scan writes \`.godot/global_script_class_cache.cfg\` from the list the edito
|
|
|
60761
60775
|
a class it cannot resolve would go out of that file with it. The answer names that loss under
|
|
60762
60776
|
\`cacheLost\` and rebuilds the cache from the declarations on disk, naming what came back under
|
|
60763
60777
|
\`cacheRestored\`, so no fresh engine, CI run or clone inherits the short file. That is the same
|
|
60764
|
-
class \`unseenByEditor\` names, and the loss is not a reason to refuse the scan.
|
|
60778
|
+
class \`unseenByEditor\` names, and the loss is not a reason to refuse the scan. An editor that lost
|
|
60779
|
+
classes this way still holds the short list and writes it again on every scan. Restart it with
|
|
60780
|
+
\`editor_launch restart\` before the next \`editor_rescan\`.
|
|
60765
60781
|
|
|
60766
60782
|
The editor also scans on its own, when files change under it, and it rewrites that file just after
|
|
60767
60783
|
the scan ends. Every engine this server starts on the project (a run, a boot check, a test run, a
|
|
@@ -62647,7 +62663,15 @@ async function registerRuntime(godot, projectPath) {
|
|
|
62647
62663
|
}
|
|
62648
62664
|
var SETUP_FLAGS = new Set(["--runtime", "--no-runtime", "--no-connect", "--no-skill", "--json", "--yes"]);
|
|
62649
62665
|
function unknownOption(flag) {
|
|
62650
|
-
|
|
62666
|
+
const known = [...SETUP_FLAGS, ...HARNESSES.map((harness) => `--${harness.id}`)];
|
|
62667
|
+
const [close] = nearest(flag.slice(2), known.map((name) => name.slice(2)));
|
|
62668
|
+
const offered = close === undefined ? "" : ` Did you mean --${close}?`;
|
|
62669
|
+
return new UsageError(`Unknown option ${flag}.${offered} gdharness help lists each command's flags, and gdharness harnesses lists the harness flags.`);
|
|
62670
|
+
}
|
|
62671
|
+
function asksForHelp() {
|
|
62672
|
+
const own = args.slice(1);
|
|
62673
|
+
const handedOn = own.indexOf("--");
|
|
62674
|
+
return (handedOn === -1 ? own : own.slice(0, handedOn)).some((arg) => arg === "--help" || arg === "-h");
|
|
62651
62675
|
}
|
|
62652
62676
|
function namedHarnesses() {
|
|
62653
62677
|
const flags = args.filter((arg) => arg.startsWith("--") && !SETUP_FLAGS.has(arg));
|
|
@@ -62949,7 +62973,7 @@ function doctorReport(projectPath) {
|
|
|
62949
62973
|
async function runtime2() {
|
|
62950
62974
|
const state = args[1];
|
|
62951
62975
|
if (state !== "on" && state !== "off") {
|
|
62952
|
-
throw new UsageError(`gdharness runtime takes on or off
|
|
62976
|
+
throw new UsageError(`gdharness runtime takes on or off${state === undefined ? "" : `, not ${state}`}. Example: gdharness runtime on path/to/project`);
|
|
62953
62977
|
}
|
|
62954
62978
|
const projectPath = projectArgument(2);
|
|
62955
62979
|
const godot = await engine(projectPath);
|
|
@@ -63012,11 +63036,11 @@ Usage:
|
|
|
63012
63036
|
gdharness upgrade [project] Reinstall the addons at this version and re-pin every config
|
|
63013
63037
|
that already names gdharness. Asks nothing, and touches no
|
|
63014
63038
|
other config. Then reconnect the MCP server, and restart an
|
|
63015
|
-
open editor
|
|
63039
|
+
open editor when the upgrade output asks for it.
|
|
63016
63040
|
gdharness uninstall [project] [--<harness>]
|
|
63017
|
-
|
|
63018
|
-
|
|
63019
|
-
|
|
63041
|
+
Remove the addons, the editor plugins, the runtime
|
|
63042
|
+
autoload, the skill and gdharness's entry in every config
|
|
63043
|
+
it can parse. Other servers in those files are
|
|
63020
63044
|
untouched. A machine-wide config may serve another project,
|
|
63021
63045
|
so it is named rather than edited unless you ask by flag.
|
|
63022
63046
|
gdharness harnesses Every harness, its flag and the file it reads
|
|
@@ -63100,6 +63124,10 @@ async function main() {
|
|
|
63100
63124
|
}
|
|
63101
63125
|
const served = process.env["GDHARNESS_PROJECT"];
|
|
63102
63126
|
nameSlotTaker(command !== undefined ? `gdharness ${command}` : `a gdharness server${served === undefined || served === "" ? "" : ` of ${served}`}`);
|
|
63127
|
+
if (command !== undefined && asksForHelp()) {
|
|
63128
|
+
printHelp();
|
|
63129
|
+
return;
|
|
63130
|
+
}
|
|
63103
63131
|
if (command === "test") {
|
|
63104
63132
|
await test();
|
|
63105
63133
|
return;
|
|
@@ -954,6 +954,12 @@ func _serialize_node_tree(
|
|
|
954
954
|
_serialize_node_tree(child, depth + 1, max_depth, include_properties, named)
|
|
955
955
|
)
|
|
956
956
|
result["children"] = children
|
|
957
|
+
else:
|
|
958
|
+
# Below the depth asked for, said rather than left out: a container with nothing under it
|
|
959
|
+
# read as empty, and the buttons of a dialog six levels down were never looked for.
|
|
960
|
+
var below: int = node.get_child_count(true)
|
|
961
|
+
if below > 0:
|
|
962
|
+
result["children_not_shown"] = below
|
|
957
963
|
|
|
958
964
|
return result
|
|
959
965
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.62",
|
|
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",
|