gdharness 0.9.5 → 0.9.7
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
CHANGED
|
@@ -15382,7 +15382,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
15382
15382
|
parameters: {
|
|
15383
15383
|
projectPath: PROJECT_PATH,
|
|
15384
15384
|
preset: { type: "string", description: "Preset name from export_presets.cfg." },
|
|
15385
|
-
outputPath: {
|
|
15385
|
+
outputPath: {
|
|
15386
|
+
type: "string",
|
|
15387
|
+
description: `Where the export is written, inside the project. The directory is created if it is not there: Godot's command-line exporter refuses a missing one with "The given export path doesn't exist", which reads as a wrong path in the preset.`
|
|
15388
|
+
},
|
|
15386
15389
|
debug: { type: "boolean", description: "run: a debug export. Default false." }
|
|
15387
15390
|
},
|
|
15388
15391
|
requires: ["projectPath"],
|
|
@@ -15713,7 +15716,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15713
15716
|
},
|
|
15714
15717
|
{
|
|
15715
15718
|
name: "editor_rescan",
|
|
15716
|
-
description: "Makes the running editor scan the project filesystem, so files written outside it, and
|
|
15719
|
+
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. The scan is a change-detecting walk rather than an unconditional reparse, so a file the editor has already recorded and reads as unchanged is one it does not look inside again: a class_name usually becomes visible this way and on one project did not, with the cache on disk still missing it after the scan said it was done. project_import refresh_classes is the one that rebuilds the class list from the declarations on disk, whatever the editor thinks it has seen. Needs the editor connected.",
|
|
15717
15720
|
parameters: {
|
|
15718
15721
|
projectPath: PROJECT_PATH,
|
|
15719
15722
|
timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." }
|
|
@@ -15791,7 +15794,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15791
15794
|
says: {
|
|
15792
15795
|
type: "string",
|
|
15793
15796
|
ops: ["find"],
|
|
15794
|
-
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match."
|
|
15797
|
+
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern."
|
|
15795
15798
|
},
|
|
15796
15799
|
limit: {
|
|
15797
15800
|
type: "number",
|
|
@@ -35261,12 +35264,14 @@ class GodotLSPClient {
|
|
|
35261
35264
|
if (!this.socket) {
|
|
35262
35265
|
this.connected = false;
|
|
35263
35266
|
this.initialized = false;
|
|
35267
|
+
this.forgetOpenDocuments();
|
|
35264
35268
|
return;
|
|
35265
35269
|
}
|
|
35266
35270
|
const socketToClose = this.socket;
|
|
35267
35271
|
this.socket = null;
|
|
35268
35272
|
this.connected = false;
|
|
35269
35273
|
this.initialized = false;
|
|
35274
|
+
this.forgetOpenDocuments();
|
|
35270
35275
|
await new Promise((resolveClose) => {
|
|
35271
35276
|
socketToClose.once("close", () => {
|
|
35272
35277
|
resolveClose();
|
|
@@ -35398,6 +35403,7 @@ class GodotLSPClient {
|
|
|
35398
35403
|
this.connected = false;
|
|
35399
35404
|
this.initialized = false;
|
|
35400
35405
|
this.socket = null;
|
|
35406
|
+
this.forgetOpenDocuments();
|
|
35401
35407
|
const failure = new Error(`Godot LSP connection error: ${error.message}`);
|
|
35402
35408
|
this.rejectAllPending(failure);
|
|
35403
35409
|
this.rejectAllDiagnosticsWaiters(failure);
|
|
@@ -35406,10 +35412,14 @@ class GodotLSPClient {
|
|
|
35406
35412
|
this.connected = false;
|
|
35407
35413
|
this.initialized = false;
|
|
35408
35414
|
this.socket = null;
|
|
35415
|
+
this.forgetOpenDocuments();
|
|
35409
35416
|
const closed = new Error("Godot LSP socket closed");
|
|
35410
35417
|
this.rejectAllPending(closed);
|
|
35411
35418
|
this.rejectAllDiagnosticsWaiters(closed);
|
|
35412
35419
|
}
|
|
35420
|
+
forgetOpenDocuments() {
|
|
35421
|
+
this.documentVersions.clear();
|
|
35422
|
+
}
|
|
35413
35423
|
rejectAllPending(error) {
|
|
35414
35424
|
this.pendingRequests.forEach((pending, id) => {
|
|
35415
35425
|
clearTimeout(pending.timer);
|
|
@@ -36002,7 +36012,7 @@ var init_setup = __esm(() => {
|
|
|
36002
36012
|
|
|
36003
36013
|
// src/server.ts
|
|
36004
36014
|
import { execFile as execFile5, spawn } from "node:child_process";
|
|
36005
|
-
import { existsSync as existsSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync13, realpathSync as realpathSync2, rmSync as rmSync8 } from "node:fs";
|
|
36015
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync13, realpathSync as realpathSync2, rmSync as rmSync8 } from "node:fs";
|
|
36006
36016
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
36007
36017
|
import { basename as basename2, dirname as dirname9, join as join15, normalize as normalize3 } from "node:path";
|
|
36008
36018
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
@@ -36454,9 +36464,10 @@ class GodotServer {
|
|
|
36454
36464
|
const known = new Set([...Object.keys(spec.parameters), ...spec.operations ? ["op"] : []]);
|
|
36455
36465
|
const unknown = Object.keys(args).filter((key) => !known.has(key));
|
|
36456
36466
|
if (unknown.length > 0) {
|
|
36467
|
+
const takes = known.size === 0 ? "It takes no arguments." : `It takes: ${[...known].join(", ")}.`;
|
|
36457
36468
|
return {
|
|
36458
36469
|
ok: false,
|
|
36459
|
-
response: this.createErrorResponse(`${spec.name} does not take ${unknown.join(", ")}.
|
|
36470
|
+
response: this.createErrorResponse(`${spec.name} does not take ${unknown.join(", ")}. ${takes}`)
|
|
36460
36471
|
};
|
|
36461
36472
|
}
|
|
36462
36473
|
const wrong = wrongTypes(spec, args);
|
|
@@ -36935,6 +36946,12 @@ class GodotServer {
|
|
|
36935
36946
|
if (!engine.ok) {
|
|
36936
36947
|
return engine.response;
|
|
36937
36948
|
}
|
|
36949
|
+
const folder = dirname9(output.absolutePath);
|
|
36950
|
+
try {
|
|
36951
|
+
mkdirSync7(folder, { recursive: true });
|
|
36952
|
+
} catch (error) {
|
|
36953
|
+
return this.createErrorResponse(`Cannot create ${dirname9(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
36954
|
+
}
|
|
36938
36955
|
const debug = readBoolean(args, "debug") ?? false;
|
|
36939
36956
|
const exportArgs = [
|
|
36940
36957
|
"--headless",
|
|
@@ -38930,6 +38947,7 @@ init_class_cache();
|
|
|
38930
38947
|
init_headless();
|
|
38931
38948
|
init_resources();
|
|
38932
38949
|
init_server_version();
|
|
38950
|
+
import { spawnSync } from "node:child_process";
|
|
38933
38951
|
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
38934
38952
|
import { dirname as dirname3, join as join8 } from "node:path";
|
|
38935
38953
|
import { fileURLToPath } from "node:url";
|
|
@@ -38992,6 +39010,29 @@ async function enablePlugins(engine, projectPath, names) {
|
|
|
38992
39010
|
async function setRuntime(engine, projectPath, on) {
|
|
38993
39011
|
return on ? await runOperation2(engine, "add_autoload", { name: RUNTIME_AUTOLOAD.name, path: `res://${RUNTIME_AUTOLOAD.path}`, enabled: true }, projectPath) : await runOperation2(engine, "remove_autoload", { name: RUNTIME_AUTOLOAD.name }, projectPath);
|
|
38994
39012
|
}
|
|
39013
|
+
function autoloadPaths(settings) {
|
|
39014
|
+
const named = new Map;
|
|
39015
|
+
for (const [name, value] of Object.entries(settings["autoload"] ?? {})) {
|
|
39016
|
+
const said = typeof value === "string" ? value.replace(/^\*/, "") : "";
|
|
39017
|
+
if (said.startsWith("res://")) {
|
|
39018
|
+
named.set(name, said.slice("res://".length));
|
|
39019
|
+
}
|
|
39020
|
+
}
|
|
39021
|
+
return named;
|
|
39022
|
+
}
|
|
39023
|
+
function trackedByGit(projectPath, wanted) {
|
|
39024
|
+
if (wanted.length === 0) {
|
|
39025
|
+
return new Set;
|
|
39026
|
+
}
|
|
39027
|
+
const asked = spawnSync("git", ["ls-files", "-z", "--", ...wanted], {
|
|
39028
|
+
cwd: projectPath,
|
|
39029
|
+
encoding: "utf8"
|
|
39030
|
+
});
|
|
39031
|
+
if (asked.status !== 0) {
|
|
39032
|
+
return null;
|
|
39033
|
+
}
|
|
39034
|
+
return new Set(asked.stdout.split("\x00").filter(Boolean));
|
|
39035
|
+
}
|
|
38995
39036
|
function enabledPlugins(settings) {
|
|
38996
39037
|
const raw = settings["editor_plugins"]?.["enabled"];
|
|
38997
39038
|
if (typeof raw !== "string") {
|
|
@@ -39023,6 +39064,13 @@ function inspectProject(projectPath) {
|
|
|
39023
39064
|
}
|
|
39024
39065
|
const autoload = settings["autoload"];
|
|
39025
39066
|
const runtimeAutoload = autoload !== undefined && typeof readString(autoload, RUNTIME_AUTOLOAD.name) === "string";
|
|
39067
|
+
const named = autoloadPaths(settings);
|
|
39068
|
+
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
39069
|
+
for (const [name, path] of named) {
|
|
39070
|
+
if (tracked !== null && !tracked.has(path) && existsSync7(join8(projectPath, path))) {
|
|
39071
|
+
problems.push(`the ${name} autoload names res://${path}, which git does not carry, so a clone boots with a missing script; commit that file, or point the autoload at one the project tracks`);
|
|
39072
|
+
}
|
|
39073
|
+
}
|
|
39026
39074
|
const cached = cachedClasses(projectPath);
|
|
39027
39075
|
const staleClasses = cached === null ? [] : staleAgainst(cached, projectPath);
|
|
39028
39076
|
if (cached === null) {
|
|
@@ -39157,6 +39205,9 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
39157
39205
|
These need the runtime autoload, which \`gdharness setup\` registers. \`runtime_capture\` needs a
|
|
39158
39206
|
window and refuses headless rather than handing back the last frame anything drew.
|
|
39159
39207
|
|
|
39208
|
+
A headless game answers; a \`godot -s\` script run does not. It has no game in it, so the autoload
|
|
39209
|
+
stays quiet there rather than announcing a test tier as the project.
|
|
39210
|
+
|
|
39160
39211
|
The autoload reaches an export. \`gdharness runtime off\` in the project before shipping.
|
|
39161
39212
|
|
|
39162
39213
|
## At a breakpoint
|
|
@@ -23,6 +23,12 @@ const BIND_ADDRESS_SETTING: String = "gdharness/runtime/bind_address"
|
|
|
23
23
|
## expects. A fixed port is for a client that cannot read the announcement.
|
|
24
24
|
const PORT_SETTING: String = "gdharness/runtime/port"
|
|
25
25
|
|
|
26
|
+
## How the engine is told to run a script instead of the game: `godot -s thing.gd`.
|
|
27
|
+
const SCRIPT_FLAGS: PackedStringArray = ["-s", "--script"]
|
|
28
|
+
|
|
29
|
+
## Serve a script run anyway, for somebody driving a `-s` script rather than a game.
|
|
30
|
+
const SCRIPT_RUNS_SETTING: String = "gdharness/runtime/serve_script_runs"
|
|
31
|
+
|
|
26
32
|
var values: Values = Values.new()
|
|
27
33
|
|
|
28
34
|
# The modules are members and not locals of _init, because a Callable holds its object by id
|
|
@@ -161,6 +167,15 @@ func _start_server() -> void:
|
|
|
161
167
|
_enabled = false
|
|
162
168
|
return
|
|
163
169
|
|
|
170
|
+
# And a script run has nothing to serve. Autoloads come up for `godot -s` as well, so a test
|
|
171
|
+
# tier or a batch tool binds a loopback port and writes an announcement under the project's
|
|
172
|
+
# own path: a gate that starts sixteen engines at once announces sixteen games that are not
|
|
173
|
+
# games, and a client asking the runtime anything while they run can be answered by whichever
|
|
174
|
+
# of them replies first. Measured on two projects before it was written here.
|
|
175
|
+
if _script_run() and not bool(ProjectSettings.get_setting(SCRIPT_RUNS_SETTING, false)):
|
|
176
|
+
_enabled = false
|
|
177
|
+
return
|
|
178
|
+
|
|
164
179
|
_server = TCPServer.new()
|
|
165
180
|
# listen() defaults bind_address to "*", which exposes the game to the whole network.
|
|
166
181
|
var bind_address: String = str(ProjectSettings.get_setting(BIND_ADDRESS_SETTING, DEFAULT_BIND_ADDRESS))
|
|
@@ -180,6 +195,15 @@ func _start_server() -> void:
|
|
|
180
195
|
print("[gdharness] runtime listening on %s:%d, announced at %s" % [bind_address, _port, _announcement])
|
|
181
196
|
|
|
182
197
|
|
|
198
|
+
## Whether the engine was told to run a script rather than the game.
|
|
199
|
+
func _script_run() -> bool:
|
|
200
|
+
var given: PackedStringArray = OS.get_cmdline_args()
|
|
201
|
+
for flag: String in SCRIPT_FLAGS:
|
|
202
|
+
if given.has(flag):
|
|
203
|
+
return true
|
|
204
|
+
return false
|
|
205
|
+
|
|
206
|
+
|
|
183
207
|
## Where the announcement goes. The server derives the same path with the same precedence, so
|
|
184
208
|
## the two only meet if this stays in step with `runtimeDirectory` in src/runtime-client.ts.
|
|
185
209
|
func _announcement_directory() -> String:
|
|
@@ -160,6 +160,17 @@ static func _is_literal(pattern: String) -> bool:
|
|
|
160
160
|
return not pattern.is_empty() and not pattern.contains("*") and not pattern.contains("?")
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
## Whether [param said] is what a find asked for in [param wanted].
|
|
164
|
+
##
|
|
165
|
+
## A plain word is a contains, which is what somebody looking for the row about a person means. A
|
|
166
|
+
## pattern is a glob, because [code]namePattern[/code] beside it is one and nobody writes `*Still*`
|
|
167
|
+
## in one field meaning a glob and in the other meaning those characters. Written as a contains
|
|
168
|
+
## only, a glob matched nothing at all and an empty answer reads as a control that is not on the
|
|
169
|
+
## screen: twice in one session here, over a button that was.
|
|
170
|
+
static func _says(said: String, wanted: String) -> bool:
|
|
171
|
+
return said.containsn(wanted) if _is_literal(wanted) else said.matchn(wanted)
|
|
172
|
+
|
|
173
|
+
|
|
163
174
|
## Every filter but the name, so a find that came back empty can say how many nodes the name was
|
|
164
175
|
## the only thing standing between it and.
|
|
165
176
|
func _matches_apart_from_name(node: Node, wanted: Dictionary[String, String]) -> bool:
|
|
@@ -168,7 +179,7 @@ func _matches_apart_from_name(node: Node, wanted: Dictionary[String, String]) ->
|
|
|
168
179
|
# What this node says, rather than everything said underneath it. A row is then found by the
|
|
169
180
|
# label in it, and the path answered is that label's, which is where the words a caller is
|
|
170
181
|
# looking at actually are: matching every container above it would answer with the screen.
|
|
171
|
-
if not wanted["says"].is_empty() and not said_by(node)
|
|
182
|
+
if not wanted["says"].is_empty() and not _says(said_by(node), wanted["says"]):
|
|
172
183
|
return false
|
|
173
184
|
var script: Variant = node.get_script()
|
|
174
185
|
if not wanted["script"].is_empty():
|
package/build/index.js
CHANGED
|
@@ -10126,7 +10126,7 @@ function defectReport(where, error, godotVersion) {
|
|
|
10126
10126
|
|
|
10127
10127
|
// src/server.ts
|
|
10128
10128
|
import { execFile as execFile3, spawn } from "node:child_process";
|
|
10129
|
-
import { existsSync as existsSync7, mkdtempSync as mkdtempSync2, readdirSync as readdirSync5, readFileSync as readFileSync9, realpathSync as realpathSync2, rmSync as rmSync4 } from "node:fs";
|
|
10129
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, readdirSync as readdirSync5, readFileSync as readFileSync9, realpathSync as realpathSync2, rmSync as rmSync4 } from "node:fs";
|
|
10130
10130
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
10131
10131
|
import { basename, dirname as dirname4, join as join9, normalize as normalize2 } from "node:path";
|
|
10132
10132
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
@@ -26694,12 +26694,14 @@ class GodotLSPClient {
|
|
|
26694
26694
|
if (!this.socket) {
|
|
26695
26695
|
this.connected = false;
|
|
26696
26696
|
this.initialized = false;
|
|
26697
|
+
this.forgetOpenDocuments();
|
|
26697
26698
|
return;
|
|
26698
26699
|
}
|
|
26699
26700
|
const socketToClose = this.socket;
|
|
26700
26701
|
this.socket = null;
|
|
26701
26702
|
this.connected = false;
|
|
26702
26703
|
this.initialized = false;
|
|
26704
|
+
this.forgetOpenDocuments();
|
|
26703
26705
|
await new Promise((resolveClose) => {
|
|
26704
26706
|
socketToClose.once("close", () => {
|
|
26705
26707
|
resolveClose();
|
|
@@ -26831,6 +26833,7 @@ class GodotLSPClient {
|
|
|
26831
26833
|
this.connected = false;
|
|
26832
26834
|
this.initialized = false;
|
|
26833
26835
|
this.socket = null;
|
|
26836
|
+
this.forgetOpenDocuments();
|
|
26834
26837
|
const failure = new Error(`Godot LSP connection error: ${error.message}`);
|
|
26835
26838
|
this.rejectAllPending(failure);
|
|
26836
26839
|
this.rejectAllDiagnosticsWaiters(failure);
|
|
@@ -26839,10 +26842,14 @@ class GodotLSPClient {
|
|
|
26839
26842
|
this.connected = false;
|
|
26840
26843
|
this.initialized = false;
|
|
26841
26844
|
this.socket = null;
|
|
26845
|
+
this.forgetOpenDocuments();
|
|
26842
26846
|
const closed = new Error("Godot LSP socket closed");
|
|
26843
26847
|
this.rejectAllPending(closed);
|
|
26844
26848
|
this.rejectAllDiagnosticsWaiters(closed);
|
|
26845
26849
|
}
|
|
26850
|
+
forgetOpenDocuments() {
|
|
26851
|
+
this.documentVersions.clear();
|
|
26852
|
+
}
|
|
26846
26853
|
rejectAllPending(error) {
|
|
26847
26854
|
this.pendingRequests.forEach((pending, id) => {
|
|
26848
26855
|
clearTimeout(pending.timer);
|
|
@@ -27985,7 +27992,10 @@ var TOOL_SPECS = [
|
|
|
27985
27992
|
parameters: {
|
|
27986
27993
|
projectPath: PROJECT_PATH,
|
|
27987
27994
|
preset: { type: "string", description: "Preset name from export_presets.cfg." },
|
|
27988
|
-
outputPath: {
|
|
27995
|
+
outputPath: {
|
|
27996
|
+
type: "string",
|
|
27997
|
+
description: `Where the export is written, inside the project. The directory is created if it is not there: Godot's command-line exporter refuses a missing one with "The given export path doesn't exist", which reads as a wrong path in the preset.`
|
|
27998
|
+
},
|
|
27989
27999
|
debug: { type: "boolean", description: "run: a debug export. Default false." }
|
|
27990
28000
|
},
|
|
27991
28001
|
requires: ["projectPath"],
|
|
@@ -28316,7 +28326,7 @@ var TOOL_SPECS = [
|
|
|
28316
28326
|
},
|
|
28317
28327
|
{
|
|
28318
28328
|
name: "editor_rescan",
|
|
28319
|
-
description: "Makes the running editor scan the project filesystem, so files written outside it, and
|
|
28329
|
+
description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. The scan is a change-detecting walk rather than an unconditional reparse, so a file the editor has already recorded and reads as unchanged is one it does not look inside again: a class_name usually becomes visible this way and on one project did not, with the cache on disk still missing it after the scan said it was done. project_import refresh_classes is the one that rebuilds the class list from the declarations on disk, whatever the editor thinks it has seen. Needs the editor connected.",
|
|
28320
28330
|
parameters: {
|
|
28321
28331
|
projectPath: PROJECT_PATH,
|
|
28322
28332
|
timeoutMs: { type: "number", description: "How long to wait for the scan. Default 30000." }
|
|
@@ -28394,7 +28404,7 @@ var TOOL_SPECS = [
|
|
|
28394
28404
|
says: {
|
|
28395
28405
|
type: "string",
|
|
28396
28406
|
ops: ["find"],
|
|
28397
|
-
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match."
|
|
28407
|
+
description: "find: part of what the node has written on it, case-insensitively, which is how a button is reached by the word on it rather than by a generated path. Its own text, so a row is found by the label in it, and hidden nodes match. A bare word is a contains; write a glob and it is one, matched against the whole of what the node says, the same as namePattern."
|
|
28398
28408
|
},
|
|
28399
28409
|
limit: {
|
|
28400
28410
|
type: "number",
|
|
@@ -29234,9 +29244,10 @@ class GodotServer {
|
|
|
29234
29244
|
const known = new Set([...Object.keys(spec.parameters), ...spec.operations ? ["op"] : []]);
|
|
29235
29245
|
const unknown = Object.keys(args).filter((key) => !known.has(key));
|
|
29236
29246
|
if (unknown.length > 0) {
|
|
29247
|
+
const takes = known.size === 0 ? "It takes no arguments." : `It takes: ${[...known].join(", ")}.`;
|
|
29237
29248
|
return {
|
|
29238
29249
|
ok: false,
|
|
29239
|
-
response: this.createErrorResponse(`${spec.name} does not take ${unknown.join(", ")}.
|
|
29250
|
+
response: this.createErrorResponse(`${spec.name} does not take ${unknown.join(", ")}. ${takes}`)
|
|
29240
29251
|
};
|
|
29241
29252
|
}
|
|
29242
29253
|
const wrong = wrongTypes(spec, args);
|
|
@@ -29715,6 +29726,12 @@ class GodotServer {
|
|
|
29715
29726
|
if (!engine.ok) {
|
|
29716
29727
|
return engine.response;
|
|
29717
29728
|
}
|
|
29729
|
+
const folder = dirname4(output.absolutePath);
|
|
29730
|
+
try {
|
|
29731
|
+
mkdirSync4(folder, { recursive: true });
|
|
29732
|
+
} catch (error) {
|
|
29733
|
+
return this.createErrorResponse(`Cannot create ${dirname4(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
29734
|
+
}
|
|
29718
29735
|
const debug = readBoolean(args, "debug") ?? false;
|
|
29719
29736
|
const exportArgs = [
|
|
29720
29737
|
"--headless",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|