gdharness 1.3.1 → 1.3.3
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 +369 -97
- package/build/godot/addons/gdharness_editor/animation_reads.gd +11 -9
- package/build/godot/addons/gdharness_editor/declared_values.gd +116 -0
- package/build/godot/addons/gdharness_editor/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +5 -0
- package/build/godot/addons/gdharness_editor/file_walk.gd +134 -0
- package/build/godot/addons/gdharness_editor/file_walk.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +161 -17
- package/build/godot/addons/gdharness_editor/reading.gd +37 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +3 -1
- package/build/godot/addons/gdharness_editor/serialisation.gd +4 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +367 -23
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +133 -39
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +20 -3
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +17 -13
- package/build/godot/addons/gdharness_editor/value_shapes.gd +7 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +27 -1
- package/build/godot/addons/gdharness_runtime/serialisation.gd +4 -0
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +7 -0
- package/build/godot/operations/animation_reads.gd +11 -9
- package/build/godot/operations/classdb_queries.gd +13 -3
- package/build/godot/operations/declared_values.gd +116 -0
- package/build/godot/operations/dependencies.gd +109 -5
- package/build/godot/operations/editor_tools.gd +3 -0
- package/build/godot/operations/export_presets.gd +4 -6
- package/build/godot/operations/file_reads.gd +5 -0
- package/build/godot/operations/file_walk.gd +35 -0
- package/build/godot/operations/gdscript_analysis.gd +7 -2
- package/build/godot/operations/gdscript_authoring.gd +105 -2
- package/build/godot/operations/godot_operations.gd +2 -0
- package/build/godot/operations/import_pipeline.gd +39 -0
- package/build/godot/operations/input_actions.gd +73 -1
- package/build/godot/operations/project_config.gd +59 -3
- package/build/godot/operations/project_diagnostics.gd +27 -2
- package/build/godot/operations/property_values.gd +161 -17
- package/build/godot/operations/reading.gd +37 -0
- package/build/godot/operations/resource_files.gd +4 -1
- package/build/godot/operations/script_check.gd +16 -0
- package/build/godot/operations/serialisation.gd +4 -0
- package/build/godot/operations/value_shapes.gd +7 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -7864,7 +7864,8 @@ function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
|
7864
7864
|
if (trimmed.includes("\x00")) {
|
|
7865
7865
|
return { ok: false, reason: "Path contains a null byte." };
|
|
7866
7866
|
}
|
|
7867
|
-
const
|
|
7867
|
+
const root = resolve(projectPath);
|
|
7868
|
+
const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : isAbsolute(trimmed) && isWithinRoot(root, trimmed) ? relative(root, resolve(trimmed)).split(sep).join("/") : trimmed;
|
|
7868
7869
|
if (withoutScheme.startsWith("uid://")) {
|
|
7869
7870
|
return {
|
|
7870
7871
|
ok: false,
|
|
@@ -7881,7 +7882,7 @@ function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
|
7881
7882
|
if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
|
|
7882
7883
|
return {
|
|
7883
7884
|
ok: false,
|
|
7884
|
-
reason: `Path '${trimmed}' is absolute; give a path inside
|
|
7885
|
+
reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as 'scenes/main.tscn'.`
|
|
7885
7886
|
};
|
|
7886
7887
|
}
|
|
7887
7888
|
const kept = keptInside(trimmed);
|
|
@@ -7897,7 +7898,6 @@ function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
|
7897
7898
|
reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
|
|
7898
7899
|
};
|
|
7899
7900
|
}
|
|
7900
|
-
const root = resolve(projectPath);
|
|
7901
7901
|
const absolutePath = resolve(root, withoutScheme);
|
|
7902
7902
|
if (!isWithinRoot(root, absolutePath)) {
|
|
7903
7903
|
return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
|
|
@@ -10678,6 +10678,14 @@ function unloadedTypes(messages, declared, cached) {
|
|
|
10678
10678
|
}
|
|
10679
10679
|
return found;
|
|
10680
10680
|
}
|
|
10681
|
+
function uncachedFor(projectPath, messagesPer) {
|
|
10682
|
+
if (!messagesPer.some((messages) => messages.some((message) => unknownTypeIn(message) !== null))) {
|
|
10683
|
+
return messagesPer.map(() => []);
|
|
10684
|
+
}
|
|
10685
|
+
const declared = declaredClasses(projectPath);
|
|
10686
|
+
const cached = cachedClasses(projectPath);
|
|
10687
|
+
return messagesPer.map((messages) => unloadedTypes(messages, declared, cached).filter((type) => !type.inTheClassCache));
|
|
10688
|
+
}
|
|
10681
10689
|
function classesNamedIn(source, known) {
|
|
10682
10690
|
const named = new Set;
|
|
10683
10691
|
for (const [word] of source.matchAll(/\b[A-Z][A-Za-z0-9_]*\b/g)) {
|
|
@@ -10818,6 +10826,16 @@ function uncachedClassNote(types) {
|
|
|
10818
10826
|
const one = types.length === 1;
|
|
10819
10827
|
return `${types.join(", ")} ${one ? "is declared" : "are declared"} in this project and missing from ` + `.godot/global_script_class_cache.cfg, so a game launched now would not resolve ${one ? "it" : "them"} ` + "either. Try editor_rescan first: it asks the editor already running and starts nothing, and it " + "cleared this in 314ms in a second project with a 31-worker fan-out still importing. It is not " + "always enough, because a file another engine has already imported reads as settled and the " + "walk does not look inside it. project_import refresh_classes rewrites the cache from the " + "declarations on disk and does reach that case, at the cost of a short headless engine, which " + "is worth knowing when something else is already running against this project.";
|
|
10820
10828
|
}
|
|
10829
|
+
function uncachedForTheEngineNote(projectPath, types) {
|
|
10830
|
+
if (types.length === 0) {
|
|
10831
|
+
return "";
|
|
10832
|
+
}
|
|
10833
|
+
const one = types.length === 1;
|
|
10834
|
+
const named = types.map((type) => `${type.type} (${type.declaredIn})`).join(", ");
|
|
10835
|
+
const cacheExists = existsSync11(join12(projectPath, ".godot", "global_script_class_cache.cfg"));
|
|
10836
|
+
const state = cacheExists ? `missing from .godot/global_script_class_cache.cfg, which is where the engine that compiled these scripts resolves a class name from, so the error is the cache being behind the files rather than the script.` : `the engine that compiled these scripts resolves a class name from .godot/global_script_class_cache.cfg, which has not been written yet, as in a project no editor has opened, so the error is the missing cache rather than the script.`;
|
|
10837
|
+
return `${named} ${one ? "is" : "are"} declared in this project${cacheExists ? " and" : ", but"} ${state} ` + "project_import refresh_classes writes the cache from the declarations on disk; editor_run start " + "writes it before a game, and an editor opening the project writes it when it scans.";
|
|
10838
|
+
}
|
|
10821
10839
|
function cacheWrittenAt(projectPath) {
|
|
10822
10840
|
const cache = join12(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
10823
10841
|
try {
|
|
@@ -11046,6 +11064,17 @@ function ignoredTexts(projectPath) {
|
|
|
11046
11064
|
visit(projectPath, "", false);
|
|
11047
11065
|
return found;
|
|
11048
11066
|
}
|
|
11067
|
+
function plainMatch(line, needle, caseSensitive) {
|
|
11068
|
+
if (caseSensitive) {
|
|
11069
|
+
return line.includes(needle) ? [needle] : null;
|
|
11070
|
+
}
|
|
11071
|
+
const lowered = line.toLowerCase();
|
|
11072
|
+
const at = lowered.indexOf(needle);
|
|
11073
|
+
if (at < 0) {
|
|
11074
|
+
return null;
|
|
11075
|
+
}
|
|
11076
|
+
return [lowered.length === line.length ? line.slice(at, at + needle.length) : needle];
|
|
11077
|
+
}
|
|
11049
11078
|
function searchProject(projectPath, options) {
|
|
11050
11079
|
const extensions = options.fileTypes === undefined ? null : new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
|
|
11051
11080
|
const result = {
|
|
@@ -11083,7 +11112,7 @@ function searchProject(projectPath, options) {
|
|
|
11083
11112
|
result.summary.files_searched += 1;
|
|
11084
11113
|
const matches = [];
|
|
11085
11114
|
for (const [index, line] of bytes.toString("utf8").split(/\r?\n/).entries()) {
|
|
11086
|
-
const found = regex ? regex.exec(line) : (
|
|
11115
|
+
const found = regex ? regex.exec(line) : plainMatch(line, needle, options.caseSensitive);
|
|
11087
11116
|
if (found === null) {
|
|
11088
11117
|
continue;
|
|
11089
11118
|
}
|
|
@@ -11091,7 +11120,7 @@ function searchProject(projectPath, options) {
|
|
|
11091
11120
|
result.summary.truncated = true;
|
|
11092
11121
|
break;
|
|
11093
11122
|
}
|
|
11094
|
-
matches.push({ line: index + 1, content: line.trim(), match: found[0]
|
|
11123
|
+
matches.push({ line: index + 1, content: line.trim(), match: found[0] });
|
|
11095
11124
|
result.summary.total_matches += 1;
|
|
11096
11125
|
}
|
|
11097
11126
|
if (matches.length > 0) {
|
|
@@ -11879,6 +11908,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11879
11908
|
init_launch();
|
|
11880
11909
|
PROJECT_INFO_SECTIONS = [
|
|
11881
11910
|
"autoloads",
|
|
11911
|
+
"input_actions",
|
|
11882
11912
|
"plugins",
|
|
11883
11913
|
"export_presets",
|
|
11884
11914
|
"audio_buses",
|
|
@@ -11912,7 +11942,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11912
11942
|
};
|
|
11913
11943
|
PROPERTIES = {
|
|
11914
11944
|
type: "object",
|
|
11915
|
-
description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. One carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds. ${WHOLE_NUMBERS}`,
|
|
11945
|
+
description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. One carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds. An enum property takes the name of a value, as the script spells it, or its number; a value past an export range, or a number no value of an enum has, is written as given and named under outsideDeclared. ${WHOLE_NUMBERS}`,
|
|
11916
11946
|
additionalProperties: true
|
|
11917
11947
|
};
|
|
11918
11948
|
XY = {
|
|
@@ -12005,7 +12035,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12005
12035
|
};
|
|
12006
12036
|
TILESET_SOURCES = {
|
|
12007
12037
|
type: "array",
|
|
12008
|
-
description: "Atlas sources, one per texture.",
|
|
12038
|
+
description: "Atlas sources, one per texture. A wholly transparent cell gets no tile, and the answer lists those cells as leftBlank; a strip at the right or bottom that no whole tile reaches is described in notInAnyTile. A path that already exists is refused, as create_tileset would replace the whole TileSet.",
|
|
12009
12039
|
items: {
|
|
12010
12040
|
type: "object",
|
|
12011
12041
|
properties: {
|
|
@@ -12025,17 +12055,17 @@ var init_tool_definitions = __esm(() => {
|
|
|
12025
12055
|
type: "object",
|
|
12026
12056
|
properties: {
|
|
12027
12057
|
coords: XY,
|
|
12028
|
-
sourceId: { type: "number", description: "TileSet source
|
|
12029
|
-
atlasCoords: XY,
|
|
12058
|
+
sourceId: { type: "number", description: "TileSet source id, or -1 to erase the cell." },
|
|
12059
|
+
atlasCoords: { ...XY, description: "The tile in the source. Needed by every cell but an erased one." },
|
|
12030
12060
|
alternativeTile: { type: "number" }
|
|
12031
12061
|
},
|
|
12032
|
-
required: ["coords", "sourceId"
|
|
12062
|
+
required: ["coords", "sourceId"],
|
|
12033
12063
|
additionalProperties: false
|
|
12034
12064
|
}
|
|
12035
12065
|
};
|
|
12036
12066
|
COLOUR2 = {
|
|
12037
|
-
type: "object",
|
|
12038
|
-
description:
|
|
12067
|
+
type: ["object", "string"],
|
|
12068
|
+
description: 'set_theme_color: r, g and b from 0 to 1, with a optional and opaque by default, or an html code such as "#ff8000" or "#ff800080".',
|
|
12039
12069
|
properties: { r: { type: "number" }, g: { type: "number" }, b: { type: "number" }, a: { type: "number" } },
|
|
12040
12070
|
required: ["r", "g", "b"],
|
|
12041
12071
|
additionalProperties: false
|
|
@@ -12051,7 +12081,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12051
12081
|
include: {
|
|
12052
12082
|
type: "array",
|
|
12053
12083
|
items: { type: "string", enum: [...PROJECT_INFO_SECTIONS] },
|
|
12054
|
-
description: "Extra sections: registered autoloads, addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation."
|
|
12084
|
+
description: "Extra sections: registered autoloads, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, and each file a scene, resource or script refers to that does not exist."
|
|
12055
12085
|
},
|
|
12056
12086
|
preset: { type: "string", description: "For validation: the export preset to validate against." },
|
|
12057
12087
|
detail: {
|
|
@@ -12065,7 +12095,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12065
12095
|
{
|
|
12066
12096
|
name: "project_settings",
|
|
12067
12097
|
title: "Project settings",
|
|
12068
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, or one on another release's addon that was not asked, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. 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. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change moves no other line; the save is the engine's, and like the editor's it writes Godot's header and config/features into a file that lacks them. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
12098
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. 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 changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, or one on another release's addon that was not asked, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. 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. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change moves no other line; the save is the engine's, and like the editor's it writes Godot's header and config/features into a file that lacks them. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
12069
12099
|
parameters: {
|
|
12070
12100
|
projectPath: PROJECT_PATH,
|
|
12071
12101
|
setting: {
|
|
@@ -12087,7 +12117,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12087
12117
|
value: {
|
|
12088
12118
|
blank: true,
|
|
12089
12119
|
ops: ["set"],
|
|
12090
|
-
description: 'The value to write, fitted to the type the setting is registered as, the way scene_node set fits a property. Engine types may be written as their fields, {"x": 1, "y": 2}, or tagged, {"_type": "Vector2", "x": 1, "y": 2}, and infinity as {"_type": "float", "value": "inf"}, the form a read answers with; one carrying a field its type has not is refused naming it, and a colour may also be its html code, "#ff8000". "" writes an empty string.'
|
|
12120
|
+
description: 'The value to write, fitted to the type the setting is registered as, the way scene_node set fits a property. Engine types may be written as their fields, {"x": 1, "y": 2}, or tagged, {"_type": "Vector2", "x": 1, "y": 2}, and infinity as {"_type": "float", "value": "inf"}, the form a read answers with; one carrying a field its type has not is refused naming it, and a colour may also be its html code, "#ff8000". "" writes an empty string. A setting the engine does not declare is written as given, a whole number as an int, and when one in its section is named like it, note names the nearest, as it may be a misspelt one.'
|
|
12091
12121
|
},
|
|
12092
12122
|
name: { type: "string", ops: ["add_autoload", "remove_autoload"], description: "Autoload name." },
|
|
12093
12123
|
path: {
|
|
@@ -12222,7 +12252,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12222
12252
|
direction: {
|
|
12223
12253
|
type: "string",
|
|
12224
12254
|
enum: ["forward", "reverse"],
|
|
12225
|
-
description: "forward: what this resource loads, with cycles reported, a script's dependencies including every script whose class_name it uses, marked by_class with the name. summary.total_dependencies counts each file once however many places load it,
|
|
12255
|
+
description: "forward: what this resource loads, with cycles reported, a script's dependencies including every script whose class_name it uses, marked by_class with the name. summary.total_dependencies counts each file once however many places load it, total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well, answered with exists: false, a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name. Each reference carries a kind: ext_resource, extends, preload, load or path for a reference by path; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about. Two kinds are mentions rather than uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs, and comment is one in ordinary prose, which it would only make stale. summary.in_code is the count with both of those left out, which is the number to read when the question is whether anything still uses this. Default forward."
|
|
12226
12256
|
},
|
|
12227
12257
|
depth: { type: "number", description: "forward: how many levels to follow. Default unlimited." },
|
|
12228
12258
|
includeBuiltin: {
|
|
@@ -12287,7 +12317,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12287
12317
|
},
|
|
12288
12318
|
reimport: {
|
|
12289
12319
|
effect: "destructive",
|
|
12290
|
-
summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. The editor scans first, since its reimport passes over a file it has not taken in, such as one an installer has just written; what that scan imported is named under importedByTheScan and is not imported again unless force asks. Answered from a second status read: reimported lists what is current now, notReimported what is not, one entry per status and reason with the paths sharing them, and via says which of the two did the work. When nothing needs reimporting, nothing runs, and the answer has the two lists empty, a note saying so, and no via. A whole-project reimport also answers missingSource, the .import files whose source is not beside them, empty when there are none, whether or not anything needed reimporting; one resourcePath is not a walk of the project and leaves it out. stillImporting is an editor that had not finished within timeoutMs",
|
|
12320
|
+
summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. The editor scans first, since its reimport passes over a file it has not taken in, such as one an installer has just written; what that scan imported is named under importedByTheScan and is not imported again unless force asks. Answered from a second status read: reimported lists what is current now, notReimported what is not, one entry per status and reason with the paths sharing them, and via says which of the two did the work. Either takes in every file that is behind, so a reimport of one resourcePath names under alsoImported the others it imported. When nothing needs reimporting, nothing runs, and the answer has the two lists empty, a note saying so, and no via. A whole-project reimport also answers missingSource, the .import files whose source is not beside them, empty when there are none, whether or not anything needed reimporting; one resourcePath is not a walk of the project and leaves it out. stillImporting is an editor that had not finished within timeoutMs",
|
|
12291
12321
|
requires: []
|
|
12292
12322
|
},
|
|
12293
12323
|
uid: {
|
|
@@ -12360,6 +12390,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
12360
12390
|
items: { type: "string" },
|
|
12361
12391
|
description: 'Suites or cases to leave out, as "suite_name" or "suite_name:test_name". A bare test name is read as a suite name and leaves out nothing; an entry that named a case which ran all the same comes back under ignoreNotApplied with the form that would have left it out.'
|
|
12362
12392
|
},
|
|
12393
|
+
tests: {
|
|
12394
|
+
type: "array",
|
|
12395
|
+
items: { type: "string" },
|
|
12396
|
+
description: 'Run only these cases of the suites under path, each "test_name" or "suite_name:test_name", the suite by its file name without .gd or its res:// path. Only the suites holding them run, with their other cases left out. A name no suite there declares as a top-level func is refused before the engine starts, with the nearest names; a case asked for that gdUnit4 did not report comes back under testsNotRun, and the run is not called passed.'
|
|
12397
|
+
},
|
|
12363
12398
|
engineArgs: {
|
|
12364
12399
|
type: "array",
|
|
12365
12400
|
items: { type: "string" },
|
|
@@ -12384,7 +12419,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12384
12419
|
{
|
|
12385
12420
|
name: "scene_create",
|
|
12386
12421
|
title: "Create or save a scene",
|
|
12387
|
-
description: `Creates a scene file where none is, saves one, or saves a copy under a new path. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
|
|
12422
|
+
description: `Creates a scene file where none is, saves one, or saves a copy under a new path. create makes a folder the path names that is not there, and names it under madeFolder. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
|
|
12388
12423
|
parameters: {
|
|
12389
12424
|
projectPath: PROJECT_PATH,
|
|
12390
12425
|
scenePath: SCENE_PATH,
|
|
@@ -12571,31 +12606,40 @@ var init_tool_definitions = __esm(() => {
|
|
|
12571
12606
|
{
|
|
12572
12607
|
name: "scene_animation",
|
|
12573
12608
|
title: "Animations",
|
|
12574
|
-
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. read and read_states need no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without running the scene's scripts. The ops that write go 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. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads 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. An editor of the project that is running and has not reached this server is waited for rather than written around.",
|
|
12609
|
+
description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. add_track on a path the animation already has a track of that type for adds the keys to that track, as the editor keys a property it already animates, answering existingTrack true and keysAdded beside keys, the track's count; a key at a time that track already has a key at is refused. A key past the animation's end, where it never plays, is written and named under pastLength. remove clears the player's autoplay when it named the animation and names under statesPlayingNothing the states that played it. read and read_states need no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without running the scene's scripts. The ops that write go 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. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads 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. An editor of the project that is running and has not reached this server is waited for rather than written around.",
|
|
12575
12610
|
parameters: {
|
|
12576
12611
|
projectPath: PROJECT_PATH,
|
|
12577
12612
|
scenePath: SCENE_PATH,
|
|
12578
12613
|
playerNodePath: {
|
|
12579
12614
|
type: "string",
|
|
12580
|
-
ops: ["read", "create", "add_track"],
|
|
12615
|
+
ops: ["read", "create", "add_track", "set", "rename", "remove"],
|
|
12581
12616
|
description: "The AnimationPlayer node."
|
|
12582
12617
|
},
|
|
12583
12618
|
animationName: {
|
|
12584
12619
|
type: "string",
|
|
12585
|
-
ops: ["read", "create", "add_track", "add_state"],
|
|
12620
|
+
ops: ["read", "create", "add_track", "set", "rename", "remove", "add_state"],
|
|
12586
12621
|
description: "The animation in the player. read: the one to answer with whole, its tracks a list; without it, read lists them, each with the number of its tracks under tracks. add_state: the one that state plays."
|
|
12587
12622
|
},
|
|
12588
|
-
|
|
12623
|
+
newName: {
|
|
12624
|
+
type: "string",
|
|
12625
|
+
ops: ["rename"],
|
|
12626
|
+
description: "rename: the animation's new name in the library it is in. The player's autoplay and every state of the scene's AnimationTrees that plays it are renamed with it, named under autoplay and statesFollowed."
|
|
12627
|
+
},
|
|
12628
|
+
length: {
|
|
12629
|
+
type: "number",
|
|
12630
|
+
ops: ["create", "set"],
|
|
12631
|
+
description: "create and set: seconds, more than 0. create: default 1. set: a key past the new end is kept, as the editor keeps it, and named under pastLength, since it never plays."
|
|
12632
|
+
},
|
|
12589
12633
|
loopMode: {
|
|
12590
12634
|
type: "string",
|
|
12591
|
-
ops: ["create"],
|
|
12635
|
+
ops: ["create", "set"],
|
|
12592
12636
|
enum: ["none", "linear", "pingpong"],
|
|
12593
12637
|
description: "create: default none."
|
|
12594
12638
|
},
|
|
12595
12639
|
step: {
|
|
12596
12640
|
type: "number",
|
|
12597
|
-
ops: ["create"],
|
|
12598
|
-
description: "create: keyframe snap in seconds.
|
|
12641
|
+
ops: ["create", "set"],
|
|
12642
|
+
description: "create and set: keyframe snap in seconds, 0 or more. create: default 0.1."
|
|
12599
12643
|
},
|
|
12600
12644
|
track: { ...ANIMATION_TRACK, ops: ["add_track"] },
|
|
12601
12645
|
animTreePath: {
|
|
@@ -12657,6 +12701,21 @@ var init_tool_definitions = __esm(() => {
|
|
|
12657
12701
|
summary: "a property or method track with keyframes",
|
|
12658
12702
|
requires: ["playerNodePath", "animationName", "track"]
|
|
12659
12703
|
},
|
|
12704
|
+
set: {
|
|
12705
|
+
effect: "destructive",
|
|
12706
|
+
summary: "an animation's length, loop mode or step",
|
|
12707
|
+
requires: ["playerNodePath", "animationName"]
|
|
12708
|
+
},
|
|
12709
|
+
rename: {
|
|
12710
|
+
effect: "destructive",
|
|
12711
|
+
summary: "an animation, with what plays it",
|
|
12712
|
+
requires: ["playerNodePath", "animationName", "newName"]
|
|
12713
|
+
},
|
|
12714
|
+
remove: {
|
|
12715
|
+
effect: "destructive",
|
|
12716
|
+
summary: "an animation from its player",
|
|
12717
|
+
requires: ["playerNodePath", "animationName"]
|
|
12718
|
+
},
|
|
12660
12719
|
add_state: {
|
|
12661
12720
|
effect: "additive",
|
|
12662
12721
|
summary: "a state playing an animation, in an AnimationTree",
|
|
@@ -12672,7 +12731,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12672
12731
|
{
|
|
12673
12732
|
name: "script_edit",
|
|
12674
12733
|
title: "Edit scripts",
|
|
12675
|
-
description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written; with an editor serving the project it reloads the script there too, so the editor's answers read what was written, and editorReloaded says so (editorNote when it could not, or when a script tab holds unsaved edits that saving would write over the file). rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, of which the newest ten are kept, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
|
|
12734
|
+
description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there; a class_name another script or an engine class holds, or an extends naming no class, is refused with nothing written. modify makes every addition or none, refuses with nothing changed additions that would stop a script that parses from parsing, giving the engine's reasons, and answers with the line each one is on in the file written; with an editor serving the project it reloads the script there too, so the editor's answers read what was written, and editorReloaded says so (editorNote when it could not, or when a script tab holds unsaved edits that saving would write over the file). rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, of which the newest ten are kept, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
|
|
12676
12735
|
parameters: {
|
|
12677
12736
|
projectPath: PROJECT_PATH,
|
|
12678
12737
|
scriptPath: SCRIPT_PATH,
|
|
@@ -12755,7 +12814,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12755
12814
|
operations: {
|
|
12756
12815
|
structure: {
|
|
12757
12816
|
effect: "read",
|
|
12758
|
-
summary: "functions, variables, signals, class_name and
|
|
12817
|
+
summary: "functions, variables, signals, constants, enums, inner_classes, class_name, extends and line_count, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies",
|
|
12759
12818
|
requires: []
|
|
12760
12819
|
},
|
|
12761
12820
|
symbols: {
|
|
@@ -12776,7 +12835,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12776
12835
|
name: "script_diagnostics",
|
|
12777
12836
|
title: "Script diagnostics",
|
|
12778
12837
|
effect: "read",
|
|
12779
|
-
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine: every error that stops a script compiling, with its line and no column, dependenciesWithErrors naming the other scripts that failed while it loaded, and warnings null, since the engine reports warnings only to an editor. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. 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, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; 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. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And 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.",
|
|
12838
|
+
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine: every error that stops a script compiling, with its line and no column, dependenciesWithErrors naming the other scripts that failed while it loaded, typesMissingFromTheClassCache naming a class the project declares that the engine could not resolve because the class cache lacks it, with staleAnalysis saying what rewrites the cache, and warnings null, since the engine reports warnings only to an editor. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. 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, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; 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. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And 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.",
|
|
12780
12839
|
parameters: {
|
|
12781
12840
|
projectPath: PROJECT_PATH,
|
|
12782
12841
|
scriptPath: {
|
|
@@ -12790,7 +12849,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12790
12849
|
{
|
|
12791
12850
|
name: "resource_edit",
|
|
12792
12851
|
title: "Edit resources",
|
|
12793
|
-
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, 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 the save, and a resource where that cannot be done is refused and the file left as it was.',
|
|
12852
|
+
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, with resourceType the engine class and scriptClass the class_name of its script when it declares one, and a Theme lists its items there as "Button/colors/font_color". A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. A property holding a resource takes its res:// path; a file never imported is refused with the reimport that imports it. move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, 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 the save, and a resource where that cannot be done is refused and the file left as it was.',
|
|
12794
12853
|
parameters: {
|
|
12795
12854
|
projectPath: PROJECT_PATH,
|
|
12796
12855
|
resourcePath: {
|
|
@@ -12833,7 +12892,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12833
12892
|
preview: {
|
|
12834
12893
|
type: "boolean",
|
|
12835
12894
|
ops: ["move"],
|
|
12836
|
-
description: "move: answer with what would move and change, and write nothing. Default false."
|
|
12895
|
+
description: "move: answer with what would move and change, and write nothing. A move that would be refused is refused here too, with the same reason. Default false."
|
|
12837
12896
|
},
|
|
12838
12897
|
controlType: {
|
|
12839
12898
|
type: "string",
|
|
@@ -13716,7 +13775,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
13716
13775
|
{
|
|
13717
13776
|
name: "debug_breakpoint",
|
|
13718
13777
|
title: "Breakpoints",
|
|
13719
|
-
description: "Sets or removes a breakpoint. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it at once (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor where it was turned off again is said so under breakpointsAtRisk, here and in editor_status.",
|
|
13778
|
+
description: "Sets or removes a breakpoint. A set on a script that is not there, or past its last line, is refused, as nothing would ever stop there; a remove takes any. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it at once (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor where it was turned off again is said so under breakpointsAtRisk, here and in editor_status.",
|
|
13720
13779
|
parameters: {
|
|
13721
13780
|
projectPath: PROJECT_PATH,
|
|
13722
13781
|
scriptPath: SCRIPT_PATH,
|
|
@@ -39275,7 +39334,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
39275
39334
|
try {
|
|
39276
39335
|
scriptPath = await realpath(contained.absolutePath);
|
|
39277
39336
|
} catch {
|
|
39278
|
-
throw new ArgumentRefusal(`Script file does not exist:
|
|
39337
|
+
throw new ArgumentRefusal(`Script file does not exist: res://${contained.relativePath}`);
|
|
39279
39338
|
}
|
|
39280
39339
|
if (!isWithinRoot(projectPath, scriptPath)) {
|
|
39281
39340
|
throw new ArgumentRefusal("scriptPath resolves outside the project root boundary.");
|
|
@@ -39361,7 +39420,7 @@ var init_lsp_client = __esm(() => {
|
|
|
39361
39420
|
AnotherProjectsServer = class AnotherProjectsServer extends Refusal {
|
|
39362
39421
|
serves;
|
|
39363
39422
|
constructor(port, serves, asked) {
|
|
39364
|
-
super(`The language server on port ${port} belongs to the editor of ${serves}
|
|
39423
|
+
super(`The language server on port ${port} belongs to the editor of ${serves}; no editor of ${asked} answers there.`);
|
|
39365
39424
|
this.serves = serves;
|
|
39366
39425
|
}
|
|
39367
39426
|
};
|
|
@@ -44089,6 +44148,46 @@ var init_lifecycle_log = __esm(() => {
|
|
|
44089
44148
|
init_server_version();
|
|
44090
44149
|
});
|
|
44091
44150
|
|
|
44151
|
+
// src/nearest.ts
|
|
44152
|
+
function similarity(one, other) {
|
|
44153
|
+
if (one === other) {
|
|
44154
|
+
return 1;
|
|
44155
|
+
}
|
|
44156
|
+
if (one.length < 2 || other.length < 2) {
|
|
44157
|
+
return 0;
|
|
44158
|
+
}
|
|
44159
|
+
const pairs = (text) => Array.from({ length: text.length - 1 }, (_, at) => text.slice(at, at + 2));
|
|
44160
|
+
const ones = pairs(one);
|
|
44161
|
+
const others = pairs(other);
|
|
44162
|
+
const shared = ones.filter((pair) => others.includes(pair)).length;
|
|
44163
|
+
return 2 * shared / (ones.length + others.length);
|
|
44164
|
+
}
|
|
44165
|
+
function withNearestFiles(said, wanted, files) {
|
|
44166
|
+
const extension = /\.([^./]+)$/.exec(wanted)?.[1];
|
|
44167
|
+
if (extension === undefined) {
|
|
44168
|
+
return said;
|
|
44169
|
+
}
|
|
44170
|
+
if (files.length === 0) {
|
|
44171
|
+
return `${said}. The project holds no .${extension} file.`;
|
|
44172
|
+
}
|
|
44173
|
+
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
44174
|
+
const close = nearest(stem(wanted), files.map((file) => stem(file)));
|
|
44175
|
+
const offered = files.filter((file) => close.includes(stem(file)));
|
|
44176
|
+
if (offered.length > 0) {
|
|
44177
|
+
return `${said}. The nearest .${extension} ${offered.length === 1 ? "file is" : "files are"} ${offered.join(", ")}.`;
|
|
44178
|
+
}
|
|
44179
|
+
if (files.length <= LISTED_FILES) {
|
|
44180
|
+
return `${said}. The project's .${extension} files are ${files.join(", ")}.`;
|
|
44181
|
+
}
|
|
44182
|
+
return `${said}. None of the project's ${files.length} .${extension} files is named like it.`;
|
|
44183
|
+
}
|
|
44184
|
+
function nearest(word, names) {
|
|
44185
|
+
const wanted = word.toLowerCase();
|
|
44186
|
+
const score = (name) => similarity(name.toLowerCase(), wanted);
|
|
44187
|
+
return names.filter((name) => score(name) >= 0.5).sort((one, other) => score(other) - score(one)).slice(0, 3);
|
|
44188
|
+
}
|
|
44189
|
+
var LISTED_FILES = 12;
|
|
44190
|
+
|
|
44092
44191
|
// src/outside.ts
|
|
44093
44192
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
44094
44193
|
function changesFrom(env, base) {
|
|
@@ -45022,6 +45121,70 @@ var init_server_instructions = __esm(() => {
|
|
|
45022
45121
|
init_tool_definitions();
|
|
45023
45122
|
});
|
|
45024
45123
|
|
|
45124
|
+
// src/test-selection.ts
|
|
45125
|
+
import { existsSync as existsSync26, readFileSync as readFileSync28 } from "node:fs";
|
|
45126
|
+
import { basename as basename10, join as join33 } from "node:path";
|
|
45127
|
+
function casesIn(projectPath, suite) {
|
|
45128
|
+
const text = readFileSync28(join33(projectPath, suite.slice("res://".length)), "utf8");
|
|
45129
|
+
return [...text.matchAll(/^func\s+(test_\w*)\s*\(/gm)].map((match) => match[1] ?? "");
|
|
45130
|
+
}
|
|
45131
|
+
function suitesUnder(projectPath, asked) {
|
|
45132
|
+
const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
|
|
45133
|
+
return [
|
|
45134
|
+
...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync26(join33(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
|
|
45135
|
+
];
|
|
45136
|
+
}
|
|
45137
|
+
function selectTests(projectPath, asked, given) {
|
|
45138
|
+
if (given === undefined || given === null) {
|
|
45139
|
+
return { ok: true, value: null };
|
|
45140
|
+
}
|
|
45141
|
+
const entries = Array.isArray(given) ? given : [given];
|
|
45142
|
+
if (entries.length === 0) {
|
|
45143
|
+
return { ok: false, reason: "tests is an empty list: name a case, or leave tests out to run every one." };
|
|
45144
|
+
}
|
|
45145
|
+
const declared = suitesUnder(projectPath, asked).flatMap((suite) => casesIn(projectPath, suite).map((test) => ({ suite, test })));
|
|
45146
|
+
const nameOf = (suite) => basename10(suite, ".gd");
|
|
45147
|
+
const selected = [];
|
|
45148
|
+
for (const [index, entry] of entries.entries()) {
|
|
45149
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
45150
|
+
return {
|
|
45151
|
+
ok: false,
|
|
45152
|
+
reason: `tests[${index}] is not a case name: give "test_name" or "suite:test_name".`
|
|
45153
|
+
};
|
|
45154
|
+
}
|
|
45155
|
+
const spelled = entry.trim();
|
|
45156
|
+
const at = spelled.lastIndexOf(":");
|
|
45157
|
+
const suiteNamed = at === -1 || spelled.slice(at).startsWith("://") ? null : spelled.slice(0, at);
|
|
45158
|
+
const test = suiteNamed === null ? spelled : spelled.slice(at + 1);
|
|
45159
|
+
const suitePath = suiteNamed?.startsWith("res://") === true ? suiteNamed : null;
|
|
45160
|
+
const matches = declared.filter((one) => one.test === test && (suiteNamed === null || one.suite === suitePath || nameOf(one.suite) === suiteNamed));
|
|
45161
|
+
if (matches.length === 0) {
|
|
45162
|
+
const said = `No suite under ${asked.join(", ")} has a case ${spelled}`;
|
|
45163
|
+
if (declared.length === 0) {
|
|
45164
|
+
return { ok: false, reason: `${said}: none of them declares a func test_ at the top level.` };
|
|
45165
|
+
}
|
|
45166
|
+
const written = declared.map((one) => `${nameOf(one.suite)}:${one.test}`);
|
|
45167
|
+
const close = nearest(suiteNamed === null ? test : `${nameOf(suiteNamed)}:${test}`, [
|
|
45168
|
+
...new Set(suiteNamed === null ? declared.map((one) => one.test) : written)
|
|
45169
|
+
]);
|
|
45170
|
+
return {
|
|
45171
|
+
ok: false,
|
|
45172
|
+
reason: close.length > 0 ? `${said}. The nearest ${close.length === 1 ? "is" : "are"} ${close.join(", ")}.` : `${said}. The cases there are ${written.slice(0, 12).join(", ")}${written.length > 12 ? `, and ${written.length - 12} more` : ""}.`
|
|
45173
|
+
};
|
|
45174
|
+
}
|
|
45175
|
+
for (const one of matches) {
|
|
45176
|
+
selected.push({ entry: spelled, ...one });
|
|
45177
|
+
}
|
|
45178
|
+
}
|
|
45179
|
+
const suites = [...new Set(selected.map((one) => one.suite))];
|
|
45180
|
+
const chosen = new Set(selected.map((one) => `${one.suite}:${one.test}`));
|
|
45181
|
+
const ignore = declared.filter((one) => suites.includes(one.suite) && !chosen.has(`${one.suite}:${one.test}`)).map((one) => `${one.suite}:${one.test}`);
|
|
45182
|
+
return { ok: true, value: { suites, ignore, selected } };
|
|
45183
|
+
}
|
|
45184
|
+
var init_test_selection = __esm(() => {
|
|
45185
|
+
init_project_scan();
|
|
45186
|
+
});
|
|
45187
|
+
|
|
45025
45188
|
// src/server.ts
|
|
45026
45189
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
45027
45190
|
import {
|
|
@@ -45029,18 +45192,18 @@ import {
|
|
|
45029
45192
|
closeSync as closeSync3,
|
|
45030
45193
|
constants,
|
|
45031
45194
|
copyFileSync,
|
|
45032
|
-
existsSync as
|
|
45195
|
+
existsSync as existsSync27,
|
|
45033
45196
|
fstatSync,
|
|
45034
45197
|
mkdirSync as mkdirSync17,
|
|
45035
45198
|
openSync as openSync4,
|
|
45036
45199
|
readdirSync as readdirSync15,
|
|
45037
|
-
readFileSync as
|
|
45200
|
+
readFileSync as readFileSync29,
|
|
45038
45201
|
readSync as readSync2,
|
|
45039
45202
|
rmdirSync as rmdirSync2,
|
|
45040
45203
|
statSync as statSync15,
|
|
45041
45204
|
writeFileSync as writeFileSync18
|
|
45042
45205
|
} from "node:fs";
|
|
45043
|
-
import { basename as
|
|
45206
|
+
import { basename as basename11, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as join34, normalize as normalize2, relative as relative6 } from "node:path";
|
|
45044
45207
|
import { setTimeout as delay5 } from "node:timers/promises";
|
|
45045
45208
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
45046
45209
|
function editorRestartTimeoutMs() {
|
|
@@ -45077,15 +45240,15 @@ function writtenFileRefusal(path, rule) {
|
|
|
45077
45240
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
45078
45241
|
}
|
|
45079
45242
|
const directory = realPathOr(dirname23(path));
|
|
45080
|
-
if (!
|
|
45243
|
+
if (!existsSync27(directory)) {
|
|
45081
45244
|
return `${named}'s directory ${dirname23(path)} does not exist; ${what} creates no directories.`;
|
|
45082
45245
|
}
|
|
45083
|
-
const where =
|
|
45246
|
+
const where = join34(directory, basename11(path));
|
|
45084
45247
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
45085
45248
|
if (inside !== undefined) {
|
|
45086
45249
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
45087
45250
|
}
|
|
45088
|
-
const taken = rule.writes(where).find((file) =>
|
|
45251
|
+
const taken = rule.writes(where).find((file) => existsSync27(file));
|
|
45089
45252
|
if (taken !== undefined) {
|
|
45090
45253
|
return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
|
|
45091
45254
|
}
|
|
@@ -45469,6 +45632,16 @@ function endedToStartThis(ended) {
|
|
|
45469
45632
|
}
|
|
45470
45633
|
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
45471
45634
|
}
|
|
45635
|
+
function lineBeyondScript(absolutePath, relativePath, line) {
|
|
45636
|
+
const text = readFileSync29(absolutePath, "utf8");
|
|
45637
|
+
const lines = text === "" ? 0 : text.split(`
|
|
45638
|
+
`).length - (text.endsWith(`
|
|
45639
|
+
`) ? 1 : 0);
|
|
45640
|
+
if (line <= lines) {
|
|
45641
|
+
return null;
|
|
45642
|
+
}
|
|
45643
|
+
return `res://${relativePath} has ${lines} line${lines === 1 ? "" : "s"}, so there is no line ${line} to stop on.`;
|
|
45644
|
+
}
|
|
45472
45645
|
function runtimeVerdict(endpoint, after) {
|
|
45473
45646
|
if (endpoint !== null) {
|
|
45474
45647
|
return { listening: true, pid: endpoint.pid, port: endpoint.port };
|
|
@@ -45638,7 +45811,7 @@ function notRunNote(notRun, failFast) {
|
|
|
45638
45811
|
function exportPresetRefusal(projectPath, preset) {
|
|
45639
45812
|
let text;
|
|
45640
45813
|
try {
|
|
45641
|
-
text =
|
|
45814
|
+
text = readFileSync29(join34(projectPath, "export_presets.cfg"), "utf8");
|
|
45642
45815
|
} catch {
|
|
45643
45816
|
return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. project_export add_preset writes one, as the editor's Project > Export does.`;
|
|
45644
45817
|
}
|
|
@@ -45704,7 +45877,7 @@ function filesIn(folder) {
|
|
|
45704
45877
|
try {
|
|
45705
45878
|
for (const entry of readdirSync15(folder, { withFileTypes: true })) {
|
|
45706
45879
|
if (entry.isFile()) {
|
|
45707
|
-
found.set(entry.name, statSync15(
|
|
45880
|
+
found.set(entry.name, statSync15(join34(folder, entry.name)).mtimeMs);
|
|
45708
45881
|
}
|
|
45709
45882
|
}
|
|
45710
45883
|
} catch {}
|
|
@@ -45736,7 +45909,7 @@ function unsavedInEditorNote(scripts) {
|
|
|
45736
45909
|
return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
|
|
45737
45910
|
}
|
|
45738
45911
|
function editorAddonMissing(projectPath) {
|
|
45739
|
-
if (!
|
|
45912
|
+
if (!existsSync27(join34(projectPath, "project.godot"))) {
|
|
45740
45913
|
return null;
|
|
45741
45914
|
}
|
|
45742
45915
|
const report = inspectProject(projectPath);
|
|
@@ -46001,7 +46174,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
46001
46174
|
let projectGodot = null;
|
|
46002
46175
|
if (projectPath !== null) {
|
|
46003
46176
|
try {
|
|
46004
|
-
projectGodot =
|
|
46177
|
+
projectGodot = readFileSync29(join34(projectPath, "project.godot"), "utf8");
|
|
46005
46178
|
} catch {}
|
|
46006
46179
|
}
|
|
46007
46180
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -46025,7 +46198,7 @@ function projectSourceOf(projectPath) {
|
|
|
46025
46198
|
let source = null;
|
|
46026
46199
|
if (inside.ok) {
|
|
46027
46200
|
try {
|
|
46028
|
-
source =
|
|
46201
|
+
source = readFileSync29(inside.absolutePath, "utf8");
|
|
46029
46202
|
} catch {}
|
|
46030
46203
|
}
|
|
46031
46204
|
read.set(script, source);
|
|
@@ -46180,7 +46353,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
46180
46353
|
}
|
|
46181
46354
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
46182
46355
|
try {
|
|
46183
|
-
return settingKeys(
|
|
46356
|
+
return settingKeys(readFileSync29(join34(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
46184
46357
|
} catch {
|
|
46185
46358
|
return true;
|
|
46186
46359
|
}
|
|
@@ -46195,7 +46368,7 @@ function uncaughtNote(uncaught) {
|
|
|
46195
46368
|
}
|
|
46196
46369
|
function elsewhereIn(projectPath, asked) {
|
|
46197
46370
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
46198
|
-
if (wanted === "" ||
|
|
46371
|
+
if (wanted === "" || existsSync27(join34(projectPath, wanted))) {
|
|
46199
46372
|
return "";
|
|
46200
46373
|
}
|
|
46201
46374
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -46298,7 +46471,7 @@ function isDirectory(path) {
|
|
|
46298
46471
|
}
|
|
46299
46472
|
}
|
|
46300
46473
|
function scriptsUnder(directory, prefix) {
|
|
46301
|
-
if (
|
|
46474
|
+
if (existsSync27(join34(directory, ".gdignore"))) {
|
|
46302
46475
|
return [];
|
|
46303
46476
|
}
|
|
46304
46477
|
const found = [];
|
|
@@ -46308,7 +46481,7 @@ function scriptsUnder(directory, prefix) {
|
|
|
46308
46481
|
}
|
|
46309
46482
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
46310
46483
|
if (entry.isDirectory()) {
|
|
46311
|
-
found.push(...scriptsUnder(
|
|
46484
|
+
found.push(...scriptsUnder(join34(directory, entry.name), path));
|
|
46312
46485
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
46313
46486
|
found.push(path);
|
|
46314
46487
|
}
|
|
@@ -46316,11 +46489,11 @@ function scriptsUnder(directory, prefix) {
|
|
|
46316
46489
|
return found;
|
|
46317
46490
|
}
|
|
46318
46491
|
function mainSceneOf(projectFile) {
|
|
46319
|
-
const scene = parseProjectGodot(
|
|
46492
|
+
const scene = parseProjectGodot(readFileSync29(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
46320
46493
|
return typeof scene === "string" && scene !== "" ? scene : null;
|
|
46321
46494
|
}
|
|
46322
46495
|
function editorPlaysHeadless(projectFile) {
|
|
46323
|
-
const runArgs = parseProjectGodot(
|
|
46496
|
+
const runArgs = parseProjectGodot(readFileSync29(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
46324
46497
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
46325
46498
|
}
|
|
46326
46499
|
function ownArguments(params) {
|
|
@@ -46338,7 +46511,7 @@ function needsItsOwnProcess(asked) {
|
|
|
46338
46511
|
class GodotServer {
|
|
46339
46512
|
stdio = null;
|
|
46340
46513
|
locator = new GodotLocator;
|
|
46341
|
-
operationsScript =
|
|
46514
|
+
operationsScript = join34(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
46342
46515
|
godotBridge;
|
|
46343
46516
|
tools = buildToolDefinitions();
|
|
46344
46517
|
activeProcess = null;
|
|
@@ -46600,14 +46773,14 @@ class GodotServer {
|
|
|
46600
46773
|
}
|
|
46601
46774
|
noteTheStart() {
|
|
46602
46775
|
const cwd = process.cwd();
|
|
46603
|
-
const mcpJson =
|
|
46776
|
+
const mcpJson = join34(cwd, ".mcp.json");
|
|
46604
46777
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
46605
46778
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
46606
46779
|
writeLifecycle("started", {
|
|
46607
46780
|
argv: process.argv.slice(1),
|
|
46608
46781
|
cwd,
|
|
46609
46782
|
project: this.ownProject,
|
|
46610
|
-
mcpJson:
|
|
46783
|
+
mcpJson: existsSync27(mcpJson) ? mcpJson : null
|
|
46611
46784
|
});
|
|
46612
46785
|
if (predecessor !== null) {
|
|
46613
46786
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -46748,7 +46921,7 @@ class GodotServer {
|
|
|
46748
46921
|
return checked.response;
|
|
46749
46922
|
}
|
|
46750
46923
|
const named = args["projectPath"];
|
|
46751
|
-
if (typeof named === "string" &&
|
|
46924
|
+
if (typeof named === "string" && existsSync27(join34(named, "project.godot"))) {
|
|
46752
46925
|
this.lastProjectPath = named;
|
|
46753
46926
|
}
|
|
46754
46927
|
this.updates.refresh();
|
|
@@ -46784,7 +46957,7 @@ class GodotServer {
|
|
|
46784
46957
|
let notSaved;
|
|
46785
46958
|
try {
|
|
46786
46959
|
savedAt = answer.content.map((item, index) => {
|
|
46787
|
-
const file =
|
|
46960
|
+
const file = join34(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
|
|
46788
46961
|
writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
|
|
46789
46962
|
return file;
|
|
46790
46963
|
});
|
|
@@ -47011,7 +47184,10 @@ class GodotServer {
|
|
|
47011
47184
|
});
|
|
47012
47185
|
if (missing.length > 0) {
|
|
47013
47186
|
const where = op !== null ? `${spec.name} ${op}` : spec.name;
|
|
47014
|
-
|
|
47187
|
+
const blank = missing.filter((field) => typeof args[field] === "string");
|
|
47188
|
+
const named = missing.map((field) => blank.includes(field) ? `${field}, which was given empty` : field);
|
|
47189
|
+
const joined = named.join(blank.length > 0 ? "; " : ", ");
|
|
47190
|
+
return { ok: false, response: this.createErrorResponse(`${where} needs ${joined}.`) };
|
|
47015
47191
|
}
|
|
47016
47192
|
return { ok: true, op };
|
|
47017
47193
|
}
|
|
@@ -47139,6 +47315,12 @@ class GodotServer {
|
|
|
47139
47315
|
return await editorOrEngine("create_animation", "writes");
|
|
47140
47316
|
case "add_track":
|
|
47141
47317
|
return await editorOrEngine("add_animation_track", "writes");
|
|
47318
|
+
case "set":
|
|
47319
|
+
return await editorOrEngine("set_animation", "writes");
|
|
47320
|
+
case "rename":
|
|
47321
|
+
return await editorOrEngine("rename_animation", "writes");
|
|
47322
|
+
case "remove":
|
|
47323
|
+
return await editorOrEngine("remove_animation", "writes");
|
|
47142
47324
|
case "add_state":
|
|
47143
47325
|
return await editorOrEngine("add_animation_state", "writes");
|
|
47144
47326
|
default:
|
|
@@ -47319,6 +47501,15 @@ class GodotServer {
|
|
|
47319
47501
|
if (line === undefined) {
|
|
47320
47502
|
return this.createErrorResponse("line is required: the one-based line to stop on.");
|
|
47321
47503
|
}
|
|
47504
|
+
if (op === "set") {
|
|
47505
|
+
if (!existsSync27(located.absolutePath)) {
|
|
47506
|
+
return this.missingScript(project.value.path, located.relativePath);
|
|
47507
|
+
}
|
|
47508
|
+
const beyond = lineBeyondScript(located.absolutePath, located.relativePath, line);
|
|
47509
|
+
if (beyond !== null) {
|
|
47510
|
+
return this.createErrorResponse(beyond);
|
|
47511
|
+
}
|
|
47512
|
+
}
|
|
47322
47513
|
const editor = this.godotBridge.isConnected();
|
|
47323
47514
|
let answer;
|
|
47324
47515
|
if (editor) {
|
|
@@ -47401,8 +47592,8 @@ class GodotServer {
|
|
|
47401
47592
|
if (path === undefined) {
|
|
47402
47593
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
47403
47594
|
}
|
|
47404
|
-
const file =
|
|
47405
|
-
if (!
|
|
47595
|
+
const file = join34(path, "project.godot");
|
|
47596
|
+
if (!existsSync27(file)) {
|
|
47406
47597
|
return {
|
|
47407
47598
|
ok: false,
|
|
47408
47599
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -47927,6 +48118,13 @@ class GodotServer {
|
|
|
47927
48118
|
const viaEditor = this.editorHasOpen(projectPath);
|
|
47928
48119
|
const extra = {};
|
|
47929
48120
|
const statusAsk = resourcePath === undefined ? { includeUpToDate: true } : { resourcePath };
|
|
48121
|
+
let behindElsewhere = [];
|
|
48122
|
+
if (resourcePath !== undefined) {
|
|
48123
|
+
const whole = await this.operation("get_import_status", {}, projectPath);
|
|
48124
|
+
if (whole.ok) {
|
|
48125
|
+
behindElsewhere = importStatuses(whole.payload).filter((one) => one.status !== "up_to_date" && one.status !== "missing_source" && one.status !== "not_imported" && !targets.includes(one.path)).map((one) => one.path);
|
|
48126
|
+
}
|
|
48127
|
+
}
|
|
47930
48128
|
if (viaEditor) {
|
|
47931
48129
|
const scan = await this.handleRescanFilesystem({ projectPath, timeoutMs });
|
|
47932
48130
|
if (scan.isError === true) {
|
|
@@ -48008,6 +48206,16 @@ class GodotServer {
|
|
|
48008
48206
|
groups.set(key, group);
|
|
48009
48207
|
}
|
|
48010
48208
|
const notReimported = [...groups.values()];
|
|
48209
|
+
if (behindElsewhere.length > 0) {
|
|
48210
|
+
const whole = await this.operation("get_import_status", { includeUpToDate: true }, projectPath);
|
|
48211
|
+
if (whole.ok) {
|
|
48212
|
+
const current = new Set(importStatuses(whole.payload).filter((one) => one.status === "up_to_date").map((one) => one.path));
|
|
48213
|
+
const alsoImported = behindElsewhere.filter((path) => current.has(path));
|
|
48214
|
+
if (alsoImported.length > 0) {
|
|
48215
|
+
extra["alsoImported"] = alsoImported;
|
|
48216
|
+
}
|
|
48217
|
+
}
|
|
48218
|
+
}
|
|
48011
48219
|
return {
|
|
48012
48220
|
ok: true,
|
|
48013
48221
|
payload: {
|
|
@@ -48102,27 +48310,51 @@ class GodotServer {
|
|
|
48102
48310
|
if (!engine.ok) {
|
|
48103
48311
|
return engine.response;
|
|
48104
48312
|
}
|
|
48105
|
-
const application = parseProjectGodot(
|
|
48313
|
+
const application = parseProjectGodot(readFileSync29(project.value.file, "utf8"))["application"] ?? {};
|
|
48106
48314
|
const name = application["config/name"];
|
|
48107
48315
|
const mainScene = application["run/main_scene"];
|
|
48108
48316
|
const info = {
|
|
48109
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
48317
|
+
name: typeof name === "string" && name !== "" ? name : basename11(project.value.path),
|
|
48110
48318
|
path: project.value.path,
|
|
48111
48319
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
48112
48320
|
godotVersion: await this.godotVersion(engine.value),
|
|
48113
48321
|
structure: projectStructure(project.value.path)
|
|
48114
48322
|
};
|
|
48115
48323
|
const detailed = args["detail"] === "full";
|
|
48324
|
+
const compiledScripts = include.includes("health") || include.includes("validation") ? await this.compiledForProjectInfo(project.value.path) : undefined;
|
|
48116
48325
|
for (const section of include) {
|
|
48117
48326
|
const spec = PROJECT_INFO_SECTIONS2[section];
|
|
48118
48327
|
if (spec === undefined) {
|
|
48119
48328
|
continue;
|
|
48120
48329
|
}
|
|
48121
|
-
const
|
|
48330
|
+
const params = {
|
|
48331
|
+
...spec.params(args, detailed),
|
|
48332
|
+
...compiledScripts !== undefined && (section === "health" || section === "validation") ? { compiled: compiledScripts } : {}
|
|
48333
|
+
};
|
|
48334
|
+
const outcome = await this.operation(spec.operation, params, project.value.path);
|
|
48122
48335
|
info[section] = outcome.ok ? { ...outcome.payload, ...engineExtras(outcome) } : { error: outcome.message, ...engineExtras(outcome) };
|
|
48123
48336
|
}
|
|
48124
48337
|
return this.jsonTextResponse(info);
|
|
48125
48338
|
}
|
|
48339
|
+
async compiledForProjectInfo(projectPath) {
|
|
48340
|
+
const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
|
|
48341
|
+
if (scripts.length === 0) {
|
|
48342
|
+
return { checked: 0, failures: [] };
|
|
48343
|
+
}
|
|
48344
|
+
const checked = await this.compileScripts(projectPath, scripts);
|
|
48345
|
+
if (!checked.ok) {
|
|
48346
|
+
return { not_checked: `no headless engine could compile the scripts: ${checked.message}` };
|
|
48347
|
+
}
|
|
48348
|
+
const failures = checked.payload.scripts.map(asParams);
|
|
48349
|
+
const uncached = uncachedFor(projectPath, failures.map((failure) => (readArray(failure, "errors") ?? []).map((entry) => String(asParams(entry)["message"]))));
|
|
48350
|
+
return {
|
|
48351
|
+
checked: checked.payload.checked.length,
|
|
48352
|
+
failures: failures.map((failure, at) => {
|
|
48353
|
+
const types = uncached[at] ?? [];
|
|
48354
|
+
return types.length === 0 ? failure : { ...failure, note: uncachedForTheEngineNote(projectPath, types) };
|
|
48355
|
+
})
|
|
48356
|
+
};
|
|
48357
|
+
}
|
|
48126
48358
|
fileTypes(args) {
|
|
48127
48359
|
const named = readStringArray(args, "fileTypes");
|
|
48128
48360
|
return named === undefined ? {} : { fileTypes: named };
|
|
@@ -48200,7 +48432,7 @@ class GodotServer {
|
|
|
48200
48432
|
const exportArgs = [
|
|
48201
48433
|
"--headless",
|
|
48202
48434
|
"--log-file",
|
|
48203
|
-
|
|
48435
|
+
join34(exportLogs, "engine.log"),
|
|
48204
48436
|
"--path",
|
|
48205
48437
|
project.value.path,
|
|
48206
48438
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -48208,7 +48440,7 @@ class GodotServer {
|
|
|
48208
48440
|
output.absolutePath
|
|
48209
48441
|
];
|
|
48210
48442
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
48211
|
-
const writtenAt = () =>
|
|
48443
|
+
const writtenAt = () => existsSync27(output.absolutePath) ? statSync15(output.absolutePath).mtimeMs : null;
|
|
48212
48444
|
const caches = editorCacheDirectory(engine.value);
|
|
48213
48445
|
const exportOnce = async () => {
|
|
48214
48446
|
const before = writtenAt();
|
|
@@ -48237,7 +48469,7 @@ class GodotServer {
|
|
|
48237
48469
|
if (ending.exitSignal === null && ending.exitCode === null) {
|
|
48238
48470
|
return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
|
|
48239
48471
|
}
|
|
48240
|
-
const beside = writtenBeside(listedBefore, filesIn(folder),
|
|
48472
|
+
const beside = writtenBeside(listedBefore, filesIn(folder), basename11(output.absolutePath), dirname23(output.relativePath).replaceAll("\\", "/"));
|
|
48241
48473
|
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
|
|
48242
48474
|
}
|
|
48243
48475
|
reportTestProgress(log) {
|
|
@@ -48291,10 +48523,10 @@ class GodotServer {
|
|
|
48291
48523
|
if (movie !== null) {
|
|
48292
48524
|
return this.createErrorResponse(movie);
|
|
48293
48525
|
}
|
|
48294
|
-
const missing = paths.value.filter((path) => !
|
|
48526
|
+
const missing = paths.value.filter((path) => !existsSync27(join34(project.value.path, path.slice("res://".length))));
|
|
48295
48527
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
48296
48528
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
48297
|
-
const noRunner = !
|
|
48529
|
+
const noRunner = !existsSync27(join34(project.value.path, runner));
|
|
48298
48530
|
if (asked.length === 0) {
|
|
48299
48531
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
48300
48532
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -48322,6 +48554,10 @@ class GodotServer {
|
|
|
48322
48554
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
48323
48555
|
]);
|
|
48324
48556
|
}
|
|
48557
|
+
const selection = selectTests(project.value.path, asked, args["tests"]);
|
|
48558
|
+
if (!selection.ok) {
|
|
48559
|
+
return this.createErrorResponse(selection.reason);
|
|
48560
|
+
}
|
|
48325
48561
|
const engine = await this.engine();
|
|
48326
48562
|
if (!engine.ok) {
|
|
48327
48563
|
return engine.response;
|
|
@@ -48352,8 +48588,8 @@ class GodotServer {
|
|
|
48352
48588
|
`res://${runner}`,
|
|
48353
48589
|
"--ignoreHeadlessMode",
|
|
48354
48590
|
...readBoolean(args, "failFast") === true ? [] : ["-c"],
|
|
48355
|
-
...asked.flatMap((path) => ["-a", path]),
|
|
48356
|
-
...ignored.flatMap((entry) => ["-i", entry]),
|
|
48591
|
+
...(selection.value?.suites ?? asked).flatMap((path) => ["-a", path]),
|
|
48592
|
+
...[...ignored, ...selection.value?.ignore ?? []].flatMap((entry) => ["-i", entry]),
|
|
48357
48593
|
"-rd",
|
|
48358
48594
|
reports,
|
|
48359
48595
|
"-rc",
|
|
@@ -48410,14 +48646,14 @@ class GodotServer {
|
|
|
48410
48646
|
await run.settled;
|
|
48411
48647
|
const tookMs = Date.now() - began;
|
|
48412
48648
|
const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
|
|
48413
|
-
const reportsDir =
|
|
48649
|
+
const reportsDir = join34(project.value.path, ".godot", ...ours.split("/"));
|
|
48414
48650
|
let report = null;
|
|
48415
48651
|
let reportProblem = null;
|
|
48416
48652
|
try {
|
|
48417
|
-
const written =
|
|
48653
|
+
const written = existsSync27(reportsDir) && notCompiled === null ? readdirSync15(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
48418
48654
|
const newest = written.at(-1);
|
|
48419
48655
|
if (newest !== undefined) {
|
|
48420
|
-
report = parseJUnit(
|
|
48656
|
+
report = parseJUnit(readFileSync29(join34(reportsDir, newest, "results.xml"), "utf8"));
|
|
48421
48657
|
}
|
|
48422
48658
|
} catch (error) {
|
|
48423
48659
|
reportProblem = errorMessage(error);
|
|
@@ -48544,10 +48780,14 @@ class GodotServer {
|
|
|
48544
48780
|
const uncaughtFolded = foldedRepeats(uncaught.map(aboveTheRunner));
|
|
48545
48781
|
const listedAsUncaught = new Set(uncaughtFolded.map((entry) => entry.index));
|
|
48546
48782
|
const notApplied = ignoredYetRan(report, ignored);
|
|
48783
|
+
const testsNotRun = [
|
|
48784
|
+
...new Set((selection.value?.selected ?? []).filter((one) => !report.suites.some((suite) => (suite.path === one.suite || suite.name === basename11(one.suite, ".gd")) && suite.cases.some((ran) => ran.name === one.test || new RegExp(`^${one.test}\\W`).test(ran.name)))).map((one) => `${basename11(one.suite, ".gd")}:${one.test}`))
|
|
48785
|
+
];
|
|
48547
48786
|
return this.jsonTextResponse({
|
|
48548
|
-
passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0 && uncaught.length === 0 && missing.length === 0,
|
|
48549
|
-
verdict,
|
|
48787
|
+
passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0 && uncaught.length === 0 && missing.length === 0 && testsNotRun.length === 0,
|
|
48788
|
+
verdict: testsNotRun.length === 0 ? verdict : `${verdict}; not run: ${testsNotRun.join(", ")}`,
|
|
48550
48789
|
...missing.length > 0 ? { missing } : {},
|
|
48790
|
+
...testsNotRun.length > 0 ? { testsNotRun } : {},
|
|
48551
48791
|
...timedOut ? { timedOut, hung, silentForMs } : {},
|
|
48552
48792
|
exitCode,
|
|
48553
48793
|
exitSignal: run.exitSignal ?? undefined,
|
|
@@ -48570,6 +48810,7 @@ class GodotServer {
|
|
|
48570
48810
|
...scanned,
|
|
48571
48811
|
note: [
|
|
48572
48812
|
missingNote,
|
|
48813
|
+
testsNotRun.length > 0 ? `${testsNotRun.join(", ")} ${testsNotRun.length === 1 ? "was" : "were"} asked for and not run, so the run is not called passed: gdUnit4 runs a test_ function only in a script that extends GdUnitTestSuite, and leaves out a suite whose script does not load.` : undefined,
|
|
48573
48814
|
briefed > 0 ? `${briefed} of the ${failed.length} failing cases, those with the largest reports, are named with their first message only, since their reports would have made the answer too large to deliver; pass one case's suite path to see its reports.` : undefined,
|
|
48574
48815
|
notApplied.length > 0 ? "An ignore entry named a case that ran all the same: gdUnit4 leaves a case out only as suite_name:test_name, and reads an entry with no colon as a suite name or a piece of a suite path. ignoreNotApplied gives the form for each case that ran." : undefined,
|
|
48575
48816
|
uncaughtNote(uncaught),
|
|
@@ -48612,6 +48853,16 @@ class GodotServer {
|
|
|
48612
48853
|
return this.createErrorResponse(scripts.reason);
|
|
48613
48854
|
}
|
|
48614
48855
|
if (scripts?.value == null) {
|
|
48856
|
+
const asked = readString(args, "scriptPath");
|
|
48857
|
+
if (project.ok && asked !== undefined) {
|
|
48858
|
+
const location = resolveWithinProject(project.value.path, asked);
|
|
48859
|
+
if (!location.ok) {
|
|
48860
|
+
return this.createErrorResponse(location.reason, PATH_SOLUTIONS);
|
|
48861
|
+
}
|
|
48862
|
+
if (!existsSync27(location.absolutePath)) {
|
|
48863
|
+
return this.missingScript(project.value.path, location.relativePath);
|
|
48864
|
+
}
|
|
48865
|
+
}
|
|
48615
48866
|
const one = await this.diagnosticsOf(args);
|
|
48616
48867
|
if ("noLanguageServer" in one) {
|
|
48617
48868
|
const checked = project.ok ? await this.engineDiagnostics(project.value.path, [readString(args, "scriptPath") ?? ""], one.noLanguageServer) : { refusal: project.response };
|
|
@@ -48678,6 +48929,11 @@ class GodotServer {
|
|
|
48678
48929
|
...byEngine === null ? {} : engineChecked(byEngine)
|
|
48679
48930
|
});
|
|
48680
48931
|
}
|
|
48932
|
+
missingScript(projectPath, relativePath) {
|
|
48933
|
+
const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
|
|
48934
|
+
const wanted = `res://${relativePath}`;
|
|
48935
|
+
return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts));
|
|
48936
|
+
}
|
|
48681
48937
|
async compileScripts(projectPath, paths) {
|
|
48682
48938
|
const payload = { checked: [], failed: [], scripts: [] };
|
|
48683
48939
|
const messages = [];
|
|
@@ -48714,21 +48970,28 @@ class GodotServer {
|
|
|
48714
48970
|
String(asParams(entry)["path"]),
|
|
48715
48971
|
asParams(entry)
|
|
48716
48972
|
]));
|
|
48973
|
+
const errorsOf = scripts.map((_, at) => readArray(failures.get(named[at] ?? "") ?? {}, "errors") ?? []);
|
|
48974
|
+
const uncachedPer = uncachedFor(projectPath, errorsOf.map((errors) => errors.map((entry) => String(asParams(entry)["message"]))));
|
|
48717
48975
|
return {
|
|
48718
48976
|
answers: scripts.map((scriptPath, at) => {
|
|
48719
48977
|
const failure = failures.get(named[at] ?? "");
|
|
48720
|
-
const diagnostics = (
|
|
48978
|
+
const diagnostics = (errorsOf[at] ?? []).map((entry) => {
|
|
48721
48979
|
const { line, message } = asParams(entry);
|
|
48722
48980
|
return { severity: "error", message, ...typeof line === "number" && line > 0 ? { line } : {} };
|
|
48723
48981
|
});
|
|
48724
48982
|
const dependencies = readArray(failure ?? {}, "dependencies_with_errors");
|
|
48983
|
+
const uncached = uncachedPer[at] ?? [];
|
|
48725
48984
|
return {
|
|
48726
48985
|
scriptPath,
|
|
48727
48986
|
clean: diagnostics.length === 0,
|
|
48728
48987
|
errors: diagnostics.length,
|
|
48729
48988
|
warnings: null,
|
|
48730
48989
|
diagnostics,
|
|
48731
|
-
...dependencies === undefined ? {} : { dependenciesWithErrors: dependencies }
|
|
48990
|
+
...dependencies === undefined ? {} : { dependenciesWithErrors: dependencies },
|
|
48991
|
+
...uncached.length === 0 ? {} : {
|
|
48992
|
+
typesMissingFromTheClassCache: uncached.map(({ type, declaredIn }) => ({ type, declaredIn })),
|
|
48993
|
+
staleAnalysis: uncachedForTheEngineNote(projectPath, uncached)
|
|
48994
|
+
}
|
|
48732
48995
|
};
|
|
48733
48996
|
})
|
|
48734
48997
|
};
|
|
@@ -48778,11 +49041,11 @@ class GodotServer {
|
|
|
48778
49041
|
const cached = cachedClasses(projectPath);
|
|
48779
49042
|
const sourceOf = (resourcePath) => {
|
|
48780
49043
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
48781
|
-
if (!contained.ok || !
|
|
49044
|
+
if (!contained.ok || !existsSync27(contained.absolutePath)) {
|
|
48782
49045
|
return null;
|
|
48783
49046
|
}
|
|
48784
49047
|
try {
|
|
48785
|
-
return
|
|
49048
|
+
return readFileSync29(contained.absolutePath, "utf8");
|
|
48786
49049
|
} catch {
|
|
48787
49050
|
return null;
|
|
48788
49051
|
}
|
|
@@ -48872,11 +49135,11 @@ class GodotServer {
|
|
|
48872
49135
|
}
|
|
48873
49136
|
sourceOfClass(projectPath, resourcePath) {
|
|
48874
49137
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
48875
|
-
if (!contained.ok || !
|
|
49138
|
+
if (!contained.ok || !existsSync27(contained.absolutePath)) {
|
|
48876
49139
|
return null;
|
|
48877
49140
|
}
|
|
48878
49141
|
try {
|
|
48879
|
-
return
|
|
49142
|
+
return readFileSync29(contained.absolutePath, "utf8");
|
|
48880
49143
|
} catch {
|
|
48881
49144
|
return null;
|
|
48882
49145
|
}
|
|
@@ -49033,7 +49296,7 @@ class GodotServer {
|
|
|
49033
49296
|
...Array.isArray(stack) ? {
|
|
49034
49297
|
stack: stack.map((frame) => ({
|
|
49035
49298
|
...frame,
|
|
49036
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
49299
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join34(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
49037
49300
|
}))
|
|
49038
49301
|
} : {}
|
|
49039
49302
|
});
|
|
@@ -49312,7 +49575,7 @@ class GodotServer {
|
|
|
49312
49575
|
return null;
|
|
49313
49576
|
}
|
|
49314
49577
|
try {
|
|
49315
|
-
return settingKeys(
|
|
49578
|
+
return settingKeys(readFileSync29(join34(projectPath, "project.godot"), "utf8"));
|
|
49316
49579
|
} catch {
|
|
49317
49580
|
return null;
|
|
49318
49581
|
}
|
|
@@ -49757,7 +50020,7 @@ class GodotServer {
|
|
|
49757
50020
|
}
|
|
49758
50021
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
49759
50022
|
const budgetMs = budget.ms;
|
|
49760
|
-
if (!
|
|
50023
|
+
if (!existsSync27(join34(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
49761
50024
|
return runtimeVerdict(null, {
|
|
49762
50025
|
addon: false,
|
|
49763
50026
|
budgetMs,
|
|
@@ -50039,7 +50302,7 @@ class GodotServer {
|
|
|
50039
50302
|
return 0;
|
|
50040
50303
|
}
|
|
50041
50304
|
try {
|
|
50042
|
-
return reportAlreadyInTranscript(
|
|
50305
|
+
return reportAlreadyInTranscript(readFileSync29(game.transcript), readFileSync29(report));
|
|
50043
50306
|
} catch (error) {
|
|
50044
50307
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
50045
50308
|
return 0;
|
|
@@ -50734,7 +50997,7 @@ class GodotServer {
|
|
|
50734
50997
|
};
|
|
50735
50998
|
const going = await this.runStillGoing(run);
|
|
50736
50999
|
const projectPath = run.projectPath;
|
|
50737
|
-
const projectGone = projectPath !== null && !
|
|
51000
|
+
const projectGone = projectPath !== null && !existsSync27(join34(projectPath, "project.godot"));
|
|
50738
51001
|
if (projectGone && callersName !== undefined) {
|
|
50739
51002
|
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
50740
51003
|
}
|
|
@@ -51081,7 +51344,7 @@ class GodotServer {
|
|
|
51081
51344
|
notes.push(endedWithoutACode({
|
|
51082
51345
|
throughEditor: run.throughEditor,
|
|
51083
51346
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
51084
|
-
couldAnnounce: run.projectPath !== null &&
|
|
51347
|
+
couldAnnounce: run.projectPath !== null && existsSync27(join34(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
51085
51348
|
endedHere: typeof run.endedHere === "string",
|
|
51086
51349
|
pickedUp: run.pickedUp === true
|
|
51087
51350
|
}));
|
|
@@ -51710,8 +51973,8 @@ class GodotServer {
|
|
|
51710
51973
|
if (!scriptPath.ok) {
|
|
51711
51974
|
return this.createErrorResponse(scriptPath.reason);
|
|
51712
51975
|
}
|
|
51713
|
-
if (!
|
|
51714
|
-
return this.
|
|
51976
|
+
if (!existsSync27(scriptPath.absolutePath)) {
|
|
51977
|
+
return this.missingScript(projectPath, scriptPath.relativePath);
|
|
51715
51978
|
}
|
|
51716
51979
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
51717
51980
|
const moveTo = asked === undefined ? null : resolveWithinProject(projectPath, asked);
|
|
@@ -52124,7 +52387,7 @@ class GodotServer {
|
|
|
52124
52387
|
}
|
|
52125
52388
|
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
52126
52389
|
const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
|
|
52127
|
-
if (declared.test(
|
|
52390
|
+
if (declared.test(readFileSync29(scriptPath, "utf8"))) {
|
|
52128
52391
|
return null;
|
|
52129
52392
|
}
|
|
52130
52393
|
const checked = await this.classesTheEditorCannotSee(args);
|
|
@@ -52290,7 +52553,7 @@ class GodotServer {
|
|
|
52290
52553
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
52291
52554
|
const run = await this.currentRun();
|
|
52292
52555
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
52293
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
52556
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync27(join34(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
52294
52557
|
return null;
|
|
52295
52558
|
}
|
|
52296
52559
|
const already = runtimesAnnounced().running;
|
|
@@ -52384,7 +52647,7 @@ class GodotServer {
|
|
|
52384
52647
|
return this.createErrorResponse(staleNote);
|
|
52385
52648
|
}
|
|
52386
52649
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
52387
|
-
const screenshotPath = screenshotDir ?
|
|
52650
|
+
const screenshotPath = screenshotDir ? join34(screenshotDir, "capture.png") : null;
|
|
52388
52651
|
const refused = (message) => this.createErrorResponse(message);
|
|
52389
52652
|
const onGame = {
|
|
52390
52653
|
pid: choice.endpoint.pid,
|
|
@@ -52445,7 +52708,7 @@ class GodotServer {
|
|
|
52445
52708
|
|
|
52446
52709
|
Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}`
|
|
52447
52710
|
},
|
|
52448
|
-
{ type: "image", data:
|
|
52711
|
+
{ type: "image", data: readFileSync29(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
52449
52712
|
]
|
|
52450
52713
|
};
|
|
52451
52714
|
} finally {
|
|
@@ -52620,6 +52883,7 @@ var init_server = __esm(() => {
|
|
|
52620
52883
|
init_server_version();
|
|
52621
52884
|
init_setup();
|
|
52622
52885
|
init_spawn();
|
|
52886
|
+
init_test_selection();
|
|
52623
52887
|
init_tool_definitions();
|
|
52624
52888
|
init_tool_reference();
|
|
52625
52889
|
init_update_check();
|
|
@@ -52668,6 +52932,7 @@ var init_server = __esm(() => {
|
|
|
52668
52932
|
});
|
|
52669
52933
|
PROJECT_INFO_SECTIONS2 = dictionary({
|
|
52670
52934
|
autoloads: { operation: "list_autoloads", params: () => ({}) },
|
|
52935
|
+
input_actions: { operation: "list_input_actions", params: () => ({}) },
|
|
52671
52936
|
plugins: { operation: "list_plugins", params: () => ({}) },
|
|
52672
52937
|
export_presets: { operation: "list_export_presets", params: () => ({}) },
|
|
52673
52938
|
audio_buses: { operation: "get_audio_buses", params: () => ({}) },
|
|
@@ -52684,8 +52949,8 @@ var init_server = __esm(() => {
|
|
|
52684
52949
|
init_bridge_announce();
|
|
52685
52950
|
init_errors();
|
|
52686
52951
|
init_godot_path();
|
|
52687
|
-
import { existsSync as
|
|
52688
|
-
import { join as
|
|
52952
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
52953
|
+
import { join as join35, resolve as resolve8 } from "node:path";
|
|
52689
52954
|
|
|
52690
52955
|
// src/harnesses.ts
|
|
52691
52956
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -53829,6 +54094,13 @@ declaration and the cache can both be right while the editor stays blind to it.
|
|
|
53829
54094
|
its own, which is the measured cure and needs no change to the declaring script; \`editor_launch
|
|
53830
54095
|
restart\` also does it and costs a restart of the editor.
|
|
53831
54096
|
|
|
54097
|
+
With no editor open, the headless engine that compiles for \`script_diagnostics\` and for
|
|
54098
|
+
\`project_info\`'s health and validation resolves class names only from that cache file. A
|
|
54099
|
+
project never opened, as a fresh clone is, has no such file, so every script typed by a project
|
|
54100
|
+
class fails there. \`script_diagnostics\` names the class under
|
|
54101
|
+
\`typesMissingFromTheClassCache\`, health and validation say so beside the failure, and
|
|
54102
|
+
\`project_import refresh_classes\` writes the cache from the declarations on disk.
|
|
54103
|
+
|
|
53832
54104
|
The scan writes \`.godot/global_script_class_cache.cfg\` from the list the editor is holding, so
|
|
53833
54105
|
a class it cannot resolve would go out of that file with it. The answer names that loss under
|
|
53834
54106
|
\`cacheLost\` and rebuilds the cache from the declarations on disk, naming what came back under
|
|
@@ -53969,7 +54241,7 @@ class UsageError extends Refusal {
|
|
|
53969
54241
|
function projectArgument(at) {
|
|
53970
54242
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
53971
54243
|
const projectPath = resolve8(named ?? ".");
|
|
53972
|
-
if (!
|
|
54244
|
+
if (!existsSync28(join35(projectPath, "project.godot"))) {
|
|
53973
54245
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
53974
54246
|
}
|
|
53975
54247
|
return projectPath;
|
|
@@ -54014,7 +54286,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
54014
54286
|
return;
|
|
54015
54287
|
}
|
|
54016
54288
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
54017
|
-
if (!
|
|
54289
|
+
if (!existsSync28(join35(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
54018
54290
|
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}`);
|
|
54019
54291
|
}
|
|
54020
54292
|
}
|
|
@@ -54146,7 +54418,7 @@ async function setup() {
|
|
|
54146
54418
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
54147
54419
|
}
|
|
54148
54420
|
if (!args.includes("--no-skill")) {
|
|
54149
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
54421
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync28);
|
|
54150
54422
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
54151
54423
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
54152
54424
|
}
|
|
@@ -54260,7 +54532,7 @@ async function upgrade() {
|
|
|
54260
54532
|
moved.push(took.from);
|
|
54261
54533
|
}
|
|
54262
54534
|
}
|
|
54263
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
54535
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync28), version)) {
|
|
54264
54536
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
54265
54537
|
}
|
|
54266
54538
|
console.log(`
|