gdharness 1.3.0 → 1.3.2
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 +322 -96
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
- package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
- 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 +65 -25
- package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
- package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
- package/build/godot/addons/gdharness_editor/property_values.gd +222 -59
- package/build/godot/addons/gdharness_editor/reading.gd +65 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
- package/build/godot/addons/gdharness_editor/serialisation.gd +49 -94
- package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +175 -74
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +88 -84
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/value_shapes.gd +118 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +65 -0
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +52 -25
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
- package/build/godot/addons/gdharness_runtime/serialisation.gd +49 -94
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +118 -0
- package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
- package/build/godot/operations/animation_reads.gd +7 -19
- package/build/godot/operations/audio_buses.gd +10 -10
- package/build/godot/operations/class_cache.gd +16 -16
- package/build/godot/operations/classdb_queries.gd +25 -15
- package/build/godot/operations/declared_values.gd +116 -0
- package/build/godot/operations/dependencies.gd +141 -33
- package/build/godot/operations/editor_tools.gd +12 -12
- package/build/godot/operations/export_presets.gd +14 -14
- package/build/godot/operations/file_reads.gd +65 -25
- package/build/godot/operations/file_walk.gd +34 -0
- package/build/godot/operations/gdscript_analysis.gd +27 -22
- package/build/godot/operations/gdscript_authoring.gd +122 -17
- package/build/godot/operations/godot_operations.gd +66 -63
- package/build/godot/operations/import_pipeline.gd +59 -20
- package/build/godot/operations/input_actions.gd +90 -18
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +8 -8
- package/build/godot/operations/project_config.gd +83 -20
- package/build/godot/operations/project_diagnostics.gd +36 -11
- package/build/godot/operations/property_values.gd +222 -59
- package/build/godot/operations/reading.gd +65 -0
- package/build/godot/operations/rename_checks.gd +3 -3
- package/build/godot/operations/resource_files.gd +9 -6
- package/build/godot/operations/script_check.gd +23 -7
- package/build/godot/operations/serialisation.gd +49 -94
- package/build/godot/operations/value_shapes.gd +118 -0
- package/package.json +1 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
3
|
+
const GdhFileWalk = preload("file_walk.gd")
|
|
4
|
+
const GdhRead = preload("reading.gd")
|
|
5
|
+
const GdhLog = preload("logger.gd")
|
|
6
|
+
const GdhProjectFile = preload("project_file.gd")
|
|
7
|
+
const GdhSerialisation = preload("serialisation.gd")
|
|
8
|
+
const GdhShapes = preload("value_shapes.gd")
|
|
7
9
|
|
|
8
|
-
var _log:
|
|
9
|
-
var _values:
|
|
10
|
+
var _log: GdhLog
|
|
11
|
+
var _values: GdhSerialisation = GdhSerialisation.new()
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
func _init(p_log:
|
|
14
|
+
func _init(p_log: GdhLog) -> void:
|
|
13
15
|
_log = p_log
|
|
14
16
|
|
|
15
17
|
|
|
@@ -76,7 +78,7 @@ func _settings_under(prefix: String) -> Dictionary:
|
|
|
76
78
|
var name: String = str(property.get("name", ""))
|
|
77
79
|
if not name.begins_with(prefix):
|
|
78
80
|
continue
|
|
79
|
-
var kind: int =
|
|
81
|
+
var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
|
|
80
82
|
var value: Variant = ProjectSettings.get_setting(name)
|
|
81
83
|
var described: Dictionary = {
|
|
82
84
|
"setting": name,
|
|
@@ -113,12 +115,23 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
113
115
|
var default_value: Variant = ProjectSettings.property_get_revert(setting_path)
|
|
114
116
|
var was_default: bool = had_value and default_value != null and old_value == default_value
|
|
115
117
|
|
|
116
|
-
var final_value: Variant = _values.deserialize_value(value)
|
|
117
118
|
var wanted: int = _wanted_type(setting_path, old_value if had_value else null)
|
|
119
|
+
# A value of fields or a colour's html code is fitted as a property write fits it, so a value one
|
|
120
|
+
# of them takes the other takes too. A number is left to the check below, which refuses one the
|
|
121
|
+
# setting would cut where fitting it would cut it.
|
|
122
|
+
var final_value: Variant = _values.deserialize_value(value)
|
|
123
|
+
if final_value is Dictionary or (wanted == TYPE_COLOR and final_value is String):
|
|
124
|
+
final_value = _values.fitted(value, wanted)
|
|
118
125
|
if wanted > TYPE_NIL and final_value != null and typeof(final_value) != wanted:
|
|
119
126
|
if not _converts_faithfully(final_value, wanted):
|
|
120
127
|
return _log.failure(_cannot_hold(setting_path, wanted, final_value))
|
|
121
128
|
final_value = type_convert(final_value, wanted)
|
|
129
|
+
# JSON carries every number as a float and a new setting has no declared type to fit one to, so a
|
|
130
|
+
# whole number was saved as 800.0 beside an answer saying 800.
|
|
131
|
+
if not had_value and wanted == TYPE_NIL and final_value is float:
|
|
132
|
+
var number: float = final_value
|
|
133
|
+
if number == floorf(number) and absf(number) < 9.0e15:
|
|
134
|
+
final_value = int(number)
|
|
122
135
|
|
|
123
136
|
var answer: Dictionary = {
|
|
124
137
|
"setting_path": setting_path,
|
|
@@ -135,13 +148,36 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
135
148
|
answer["unchanged"] = true
|
|
136
149
|
return answer
|
|
137
150
|
|
|
151
|
+
var near: PackedStringArray = []
|
|
152
|
+
if not had_value:
|
|
153
|
+
near = _settings_near(setting_path)
|
|
154
|
+
if not near.is_empty():
|
|
155
|
+
answer["note"] = (
|
|
156
|
+
(
|
|
157
|
+
"No setting %s existed, so this wrote a new one, which the engine reads only where"
|
|
158
|
+
+ " something asks for it by that name. The nearest %s %s."
|
|
159
|
+
)
|
|
160
|
+
% [setting_path, "setting is" if near.size() == 1 else "settings are", ", ".join(near)]
|
|
161
|
+
)
|
|
138
162
|
ProjectSettings.set_setting(setting_path, final_value)
|
|
139
|
-
var err: Error =
|
|
163
|
+
var err: Error = GdhProjectFile.save()
|
|
140
164
|
if err != OK:
|
|
141
165
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
142
166
|
return answer
|
|
143
167
|
|
|
144
168
|
|
|
169
|
+
# The settings in the same section as [param setting_path] named nearest it, for a new one that is
|
|
170
|
+
# likely a misspelt one: a setting nothing declares is written all the same, and is never read.
|
|
171
|
+
static func _settings_near(setting_path: String) -> PackedStringArray:
|
|
172
|
+
var section: String = setting_path.get_slice("/", 0) + "/"
|
|
173
|
+
var names: PackedStringArray = []
|
|
174
|
+
for info: Dictionary in ProjectSettings.get_property_list():
|
|
175
|
+
var name: String = str(info.get("name", ""))
|
|
176
|
+
if name.begins_with(section) and name != setting_path:
|
|
177
|
+
var _added: bool = names.append(name)
|
|
178
|
+
return GdhRead.nearest(setting_path, names)
|
|
179
|
+
|
|
180
|
+
|
|
145
181
|
# The type a setting wants, or TYPE_NIL where the engine names none and there is nothing to go on.
|
|
146
182
|
#
|
|
147
183
|
# JSON carries one number type, so every number arrives as a float and an int setting was written
|
|
@@ -157,7 +193,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
157
193
|
func _wanted_type(setting_path: String, current: Variant) -> int:
|
|
158
194
|
for info: Dictionary in ProjectSettings.get_property_list():
|
|
159
195
|
if str(info.get("name", "")) == setting_path:
|
|
160
|
-
return
|
|
196
|
+
return GdhRead.as_int(info.get("type", TYPE_NIL), TYPE_NIL)
|
|
161
197
|
return typeof(current)
|
|
162
198
|
|
|
163
199
|
|
|
@@ -181,19 +217,20 @@ static func _cannot_hold(setting_path: String, wanted: int, value: Variant) -> S
|
|
|
181
217
|
if wanted in numbers and typeof(value) in numbers:
|
|
182
218
|
return "%s, and %s is not the same number as one.%s" % [declared, str(value), cost]
|
|
183
219
|
var given: String = type_string(typeof(value))
|
|
184
|
-
|
|
220
|
+
var remedy: String = GdhShapes.remedy(value, wanted)
|
|
221
|
+
return "%s, and %s (%s) cannot become one.%s" % [declared, JSON.stringify(value), given, remedy]
|
|
185
222
|
|
|
186
223
|
|
|
187
224
|
# Add an autoload singleton
|
|
188
225
|
func add_autoload(params: Dictionary) -> Dictionary:
|
|
189
226
|
var name: String = str(params.get("name", ""))
|
|
190
227
|
var path: String = str(params.get("path", ""))
|
|
191
|
-
var global: bool =
|
|
228
|
+
var global: bool = GdhRead.as_bool(params.get("global", true), true)
|
|
192
229
|
|
|
193
230
|
# There is no disabled autoload in Godot 4: every entry in the list is loaded under the root at
|
|
194
231
|
# every start. `enabled: false` used to write the entry without its asterisk and answer that it
|
|
195
232
|
# was disabled, and the autoload ran all the same.
|
|
196
|
-
if not
|
|
233
|
+
if not GdhRead.as_bool(params.get("enabled", true), true):
|
|
197
234
|
return _log.failure(
|
|
198
235
|
(
|
|
199
236
|
"An autoload cannot be added disabled: Godot loads every autoload in the list at every"
|
|
@@ -208,7 +245,9 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
208
245
|
_log.info("Adding autoload: " + name + " -> " + path)
|
|
209
246
|
|
|
210
247
|
if not FileAccess.file_exists(path):
|
|
211
|
-
return _log.failure(
|
|
248
|
+
return _log.failure(
|
|
249
|
+
GdhFileWalk.new().with_nearest_files("Autoload file does not exist: " + path, path)
|
|
250
|
+
)
|
|
212
251
|
|
|
213
252
|
# Through ProjectSettings so the file is saved the way the editor saves it, which includes
|
|
214
253
|
# putting Godot's header back and dropping any comment somebody wrote into the file by hand.
|
|
@@ -218,8 +257,18 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
218
257
|
# An asterisk in front of the path makes the autoload's name a global variable, the editor's
|
|
219
258
|
# "Global Variable" column. Without it the node still comes up under the root; only the name
|
|
220
259
|
# stops resolving.
|
|
221
|
-
|
|
222
|
-
|
|
260
|
+
var entry: String = ("*" if global else "") + path
|
|
261
|
+
if was_updated and str(ProjectSettings.get_setting(setting)) == entry:
|
|
262
|
+
return {
|
|
263
|
+
"name": name,
|
|
264
|
+
"path": path,
|
|
265
|
+
"enabled": true,
|
|
266
|
+
"global": global,
|
|
267
|
+
"action": "unchanged",
|
|
268
|
+
"saved": false
|
|
269
|
+
}
|
|
270
|
+
ProjectSettings.set_setting(setting, entry)
|
|
271
|
+
var err: Error = GdhProjectFile.save()
|
|
223
272
|
if err != OK:
|
|
224
273
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
225
274
|
|
|
@@ -229,6 +278,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
229
278
|
"enabled": true,
|
|
230
279
|
"global": global,
|
|
231
280
|
"action": "updated" if was_updated else "added",
|
|
281
|
+
"saved": true,
|
|
232
282
|
}
|
|
233
283
|
|
|
234
284
|
|
|
@@ -244,7 +294,7 @@ func remove_autoload(params: Dictionary) -> Dictionary:
|
|
|
244
294
|
|
|
245
295
|
var old_value: String = str(ProjectSettings.get_setting(setting))
|
|
246
296
|
ProjectSettings.set_setting(setting, null)
|
|
247
|
-
var err: Error =
|
|
297
|
+
var err: Error = GdhProjectFile.save()
|
|
248
298
|
if err != OK:
|
|
249
299
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
250
300
|
|
|
@@ -288,12 +338,25 @@ func set_main_scene(params: Dictionary) -> Dictionary:
|
|
|
288
338
|
_log.info("Setting main scene: " + scene_path)
|
|
289
339
|
|
|
290
340
|
if not FileAccess.file_exists(scene_path):
|
|
291
|
-
return _log.failure(
|
|
341
|
+
return _log.failure(
|
|
342
|
+
GdhFileWalk.new().with_nearest_files("Scene file does not exist: " + scene_path, scene_path)
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# The engine starts the game by instancing the main scene, so a script or any other resource set
|
|
346
|
+
# there was saved and answered as set, and the game then would not start.
|
|
347
|
+
if not ResourceLoader.exists(scene_path, "PackedScene"):
|
|
348
|
+
var loaded: Resource = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
349
|
+
var kind: String = loaded.get_class() if loaded != null else ""
|
|
350
|
+
var said: String = (
|
|
351
|
+
"%s is %s, not a scene, and the main scene has to be one"
|
|
352
|
+
% [scene_path, "a " + kind if not kind.is_empty() else "no resource the engine loads"]
|
|
353
|
+
)
|
|
354
|
+
return _log.failure(GdhFileWalk.new().with_nearest_files(said, scene_path.get_basename() + ".tscn"))
|
|
292
355
|
|
|
293
356
|
var old_main_scene: String = str(ProjectSettings.get_setting("application/run/main_scene", ""))
|
|
294
357
|
|
|
295
358
|
ProjectSettings.set_setting("application/run/main_scene", scene_path)
|
|
296
|
-
var err: Error =
|
|
359
|
+
var err: Error = GdhProjectFile.save()
|
|
297
360
|
|
|
298
361
|
if err != OK:
|
|
299
362
|
return _log.failure("Failed to save project settings: " + str(err))
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
const GdhDependencies = preload("dependencies.gd")
|
|
4
|
+
const GdhFileWalk = preload("file_walk.gd")
|
|
5
|
+
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
6
|
+
const GdhLog = preload("logger.gd")
|
|
7
|
+
const GdhRead = preload("reading.gd")
|
|
8
|
+
const GdhScriptCheck = preload("script_check.gd")
|
|
6
9
|
|
|
7
|
-
var _log:
|
|
8
|
-
var _files:
|
|
10
|
+
var _log: GdhLog
|
|
11
|
+
var _files: GdhFileWalk = GdhFileWalk.new()
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
func _init(p_log:
|
|
14
|
+
func _init(p_log: GdhLog) -> void:
|
|
12
15
|
_log = p_log
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
# Get comprehensive project health check with scoring
|
|
16
19
|
func get_project_health(params: Dictionary) -> Dictionary:
|
|
17
20
|
var check_categories: Array = params.get(
|
|
18
|
-
"categories", ["structure", "resources", "scripts", "scenes", "config"]
|
|
21
|
+
"categories", ["structure", "resources", "scripts", "references", "scenes", "config"]
|
|
19
22
|
)
|
|
20
23
|
|
|
21
24
|
_log.info("Performing project health check")
|
|
@@ -44,7 +47,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
44
47
|
# Every format the engine imports, the list the import status reads: five of them were checked
|
|
45
48
|
# here, so a project of glTF models, fonts and WebP textures had nothing to reimport.
|
|
46
49
|
var resources: Array[String] = _files.find_files_with_extensions(
|
|
47
|
-
"res://",
|
|
50
|
+
"res://", GdhImportPipeline.IMPORTABLE_EXTENSIONS
|
|
48
51
|
)
|
|
49
52
|
|
|
50
53
|
var missing_imports: int = 0
|
|
@@ -87,12 +90,34 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
87
90
|
if todo_count > 0:
|
|
88
91
|
recommendations.append("%d scripts have TODO or FIXME comments to look at" % todo_count)
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
# Compiled by the server before this runs, since an engine stops at the first script that
|
|
94
|
+
# fails, and handed in. Without it the check read only comments and said "passed" beside
|
|
95
|
+
# scripts the engine refused.
|
|
96
|
+
var compiled: Dictionary = params.get("compiled", {})
|
|
97
|
+
var failures: Array = compiled.get("failures", [])
|
|
98
|
+
for failure: Dictionary in failures:
|
|
99
|
+
details.append(GdhScriptCheck.failure_line(failure))
|
|
100
|
+
deductions += 10
|
|
101
|
+
|
|
102
|
+
var scripts: Dictionary = {
|
|
91
103
|
"name": "Scripts",
|
|
92
104
|
"passed": details.is_empty(),
|
|
93
105
|
"details": details,
|
|
94
106
|
"total_scripts": script_files.size()
|
|
95
107
|
}
|
|
108
|
+
if compiled.has("checked"):
|
|
109
|
+
scripts["compiled"] = GdhRead.as_int(compiled["checked"])
|
|
110
|
+
else:
|
|
111
|
+
scripts["not_compiled"] = str(compiled.get("not_checked", "no compile was asked for"))
|
|
112
|
+
checks["scripts"] = scripts
|
|
113
|
+
|
|
114
|
+
# 3b. References
|
|
115
|
+
if "references" in check_categories:
|
|
116
|
+
var details: Array[String] = []
|
|
117
|
+
for entry: Dictionary in GdhDependencies.new(_log).missing_references():
|
|
118
|
+
details.append(GdhDependencies.missing_line(entry))
|
|
119
|
+
deductions += 5
|
|
120
|
+
checks["references"] = {"name": "References", "passed": details.is_empty(), "details": details}
|
|
96
121
|
|
|
97
122
|
# 4. Scenes Check
|
|
98
123
|
if "scenes" in check_categories:
|
|
@@ -119,8 +144,8 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
119
144
|
if main_scene.is_empty():
|
|
120
145
|
details.append("No main scene configured")
|
|
121
146
|
deductions += 10
|
|
122
|
-
elif not
|
|
123
|
-
details.append("Main scene " +
|
|
147
|
+
elif not GdhFileWalk.missing_reason(main_scene).is_empty():
|
|
148
|
+
details.append("Main scene " + GdhFileWalk.missing_reason(main_scene))
|
|
124
149
|
deductions += 15
|
|
125
150
|
|
|
126
151
|
var project_name: String = str(ProjectSettings.get_setting("application/config/name", ""))
|