gdharness 1.2.33 → 1.2.35
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 +134 -33
- package/build/godot/addons/gdharness_editor/animation_reads.gd +224 -0
- package/build/godot/addons/gdharness_editor/animation_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +76 -5
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +30 -143
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +4 -0
- package/build/godot/operations/animation_reads.gd +224 -0
- package/build/godot/operations/export_presets.gd +232 -0
- package/build/godot/operations/file_reads.gd +76 -5
- package/build/godot/operations/godot_operations.gd +21 -1
- package/build/godot/operations/import_pipeline.gd +4 -60
- package/build/godot/operations/input_actions.gd +51 -7
- package/build/godot/operations/project_diagnostics.gd +3 -1
- package/build/index.js +132 -31
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -25071,6 +25071,12 @@ function isWithinRoot(rootPath, candidatePath) {
|
|
|
25071
25071
|
const step = relative(resolve(rootPath), resolve(candidatePath));
|
|
25072
25072
|
return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
|
|
25073
25073
|
}
|
|
25074
|
+
var KEPT_ID = /^[A-Za-z0-9_]+$/;
|
|
25075
|
+
function keptInside(given) {
|
|
25076
|
+
const at = given.indexOf("::");
|
|
25077
|
+
const inner = at === -1 ? "" : given.slice(at + 2);
|
|
25078
|
+
return KEPT_ID.test(inner) ? { file: given.slice(0, at), inner } : { file: given, inner: undefined };
|
|
25079
|
+
}
|
|
25074
25080
|
function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
25075
25081
|
const trimmed = candidatePath.trim();
|
|
25076
25082
|
if (trimmed === "") {
|
|
@@ -25099,6 +25105,13 @@ function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
|
25099
25105
|
reason: `Path '${trimmed}' is absolute; give a path inside the project, such as 'scenes/main.tscn'.`
|
|
25100
25106
|
};
|
|
25101
25107
|
}
|
|
25108
|
+
const kept = keptInside(trimmed);
|
|
25109
|
+
if (kept.inner !== undefined) {
|
|
25110
|
+
return {
|
|
25111
|
+
ok: false,
|
|
25112
|
+
reason: `Path '${trimmed}' names the resource ${kept.inner} kept inside ${kept.file}, not a file. resource_edit read reads one by that path; this argument takes a file.`
|
|
25113
|
+
};
|
|
25114
|
+
}
|
|
25102
25115
|
if (withoutScheme.includes(":")) {
|
|
25103
25116
|
return {
|
|
25104
25117
|
ok: false,
|
|
@@ -28704,9 +28717,14 @@ class GameLog {
|
|
|
28704
28717
|
const owner = this.lastHeadline[source];
|
|
28705
28718
|
if (owner !== null && /^\s/.test(line)) {
|
|
28706
28719
|
owner.detail.push(line.trim());
|
|
28720
|
+
owner.continues = false;
|
|
28707
28721
|
return;
|
|
28708
28722
|
}
|
|
28709
28723
|
const headline = announced(line);
|
|
28724
|
+
if (owner?.continues === true && headline === null) {
|
|
28725
|
+
owner.detail.push(line.trim());
|
|
28726
|
+
return;
|
|
28727
|
+
}
|
|
28710
28728
|
const detail = [];
|
|
28711
28729
|
const entry = {
|
|
28712
28730
|
index: this.entries.length,
|
|
@@ -28719,7 +28737,7 @@ class GameLog {
|
|
|
28719
28737
|
if (headline?.byScript === true) {
|
|
28720
28738
|
this.byScript.add(entry.index);
|
|
28721
28739
|
}
|
|
28722
|
-
this.lastHeadline[source] = headline ? { detail } : null;
|
|
28740
|
+
this.lastHeadline[source] = headline ? { detail, continues: source === "stderr" && headline.text.endsWith(":") } : null;
|
|
28723
28741
|
}
|
|
28724
28742
|
raisedByScript(entry) {
|
|
28725
28743
|
return this.byScript.has(entry.index);
|
|
@@ -33301,7 +33319,7 @@ var HEADLESS_OPERATIONS = dictionary({
|
|
|
33301
33319
|
uid: "get_uid",
|
|
33302
33320
|
refresh_classes: "refresh_class_cache"
|
|
33303
33321
|
}),
|
|
33304
|
-
project_export: dictionary({ list: "list_export_presets" }),
|
|
33322
|
+
project_export: dictionary({ list: "list_export_presets", add_preset: "add_export_preset" }),
|
|
33305
33323
|
script_edit: dictionary({ create: "create_script", modify: "modify_script" }),
|
|
33306
33324
|
script_info: dictionary({ structure: "get_script_info" }),
|
|
33307
33325
|
editor_classes: dictionary({
|
|
@@ -35040,7 +35058,7 @@ var TOOL_SPECS = [
|
|
|
35040
35058
|
{
|
|
35041
35059
|
name: "project_settings",
|
|
35042
35060
|
title: "Project settings",
|
|
35043
|
-
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, one on an addon from before this, 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. 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 touches only its own lines. 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.",
|
|
35061
|
+
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, one on an addon from before this, 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 touches only its own lines. 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.",
|
|
35044
35062
|
parameters: {
|
|
35045
35063
|
projectPath: PROJECT_PATH2,
|
|
35046
35064
|
setting: {
|
|
@@ -35138,8 +35156,8 @@ var TOOL_SPECS = [
|
|
|
35138
35156
|
requires: ["scenePath"]
|
|
35139
35157
|
},
|
|
35140
35158
|
add_input_action: {
|
|
35141
|
-
effect: "
|
|
35142
|
-
summary: "register an input action and its events",
|
|
35159
|
+
effect: "destructive",
|
|
35160
|
+
summary: "register an input action and its events, or rebind one that exists",
|
|
35143
35161
|
requires: ["actionName", "events"]
|
|
35144
35162
|
},
|
|
35145
35163
|
remove_input_action: {
|
|
@@ -35277,20 +35295,36 @@ var TOOL_SPECS = [
|
|
|
35277
35295
|
{
|
|
35278
35296
|
name: "project_export",
|
|
35279
35297
|
title: "Export",
|
|
35280
|
-
description: "Export presets and exports.",
|
|
35298
|
+
description: "Export presets and exports. add_preset writes a preset to export_presets.cfg with the keys the editor's Project > Export writes for a new one, every platform option left at its default, and makes it the platform's runnable preset when the platform has none. It answers where this engine's export templates belong and whether the preset's platform has them, since an export needs them; list says the same for every preset. An editor open on the project does not see a preset added this way until it restarts. run names, as alsoWritten, what else the export wrote beside its output, such as the .pck a Windows or Linux build cannot start without.",
|
|
35281
35299
|
parameters: {
|
|
35282
35300
|
projectPath: PROJECT_PATH2,
|
|
35283
|
-
preset: {
|
|
35301
|
+
preset: {
|
|
35302
|
+
type: "string",
|
|
35303
|
+
ops: ["run", "add_preset"],
|
|
35304
|
+
description: "Preset name from export_presets.cfg. add_preset: the name of the new one."
|
|
35305
|
+
},
|
|
35306
|
+
platform: {
|
|
35307
|
+
type: "string",
|
|
35308
|
+
ops: ["add_preset"],
|
|
35309
|
+
enum: ["Windows Desktop", "Linux", "macOS", "Android", "iOS", "Web"],
|
|
35310
|
+
description: "add_preset: the platform the preset exports for."
|
|
35311
|
+
},
|
|
35284
35312
|
outputPath: {
|
|
35285
35313
|
type: "string",
|
|
35286
|
-
|
|
35314
|
+
ops: ["run", "add_preset"],
|
|
35315
|
+
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. add_preset: the path the preset exports to from the editor, optional.`
|
|
35287
35316
|
},
|
|
35288
|
-
debug: { type: "boolean", description: "run: a debug export. Default false." }
|
|
35317
|
+
debug: { type: "boolean", ops: ["run"], description: "run: a debug export. Default false." }
|
|
35289
35318
|
},
|
|
35290
35319
|
requires: ["projectPath"],
|
|
35291
35320
|
operations: {
|
|
35292
35321
|
list: { effect: "read", summary: "the presets in export_presets.cfg", requires: [] },
|
|
35293
|
-
run: { effect: "destructive", summary: "export with a preset", requires: ["preset", "outputPath"] }
|
|
35322
|
+
run: { effect: "destructive", summary: "export with a preset", requires: ["preset", "outputPath"] },
|
|
35323
|
+
add_preset: {
|
|
35324
|
+
effect: "additive",
|
|
35325
|
+
summary: "add an export preset to export_presets.cfg",
|
|
35326
|
+
requires: ["preset", "platform"]
|
|
35327
|
+
}
|
|
35294
35328
|
}
|
|
35295
35329
|
},
|
|
35296
35330
|
{
|
|
@@ -35487,7 +35521,7 @@ var TOOL_SPECS = [
|
|
|
35487
35521
|
{
|
|
35488
35522
|
name: "scene_animation",
|
|
35489
35523
|
title: "Animations",
|
|
35490
|
-
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.
|
|
35524
|
+
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 need the editor connected.",
|
|
35491
35525
|
parameters: {
|
|
35492
35526
|
projectPath: PROJECT_PATH2,
|
|
35493
35527
|
scenePath: SCENE_PATH,
|
|
@@ -35519,7 +35553,11 @@ var TOOL_SPECS = [
|
|
|
35519
35553
|
ops: ["read_states", "add_state", "connect_states"],
|
|
35520
35554
|
description: "The AnimationTree node."
|
|
35521
35555
|
},
|
|
35522
|
-
stateName: {
|
|
35556
|
+
stateName: {
|
|
35557
|
+
type: "string",
|
|
35558
|
+
ops: ["add_state"],
|
|
35559
|
+
description: "add_state: the state to add. It goes right of the rightmost state in the editor's graph, answered under position."
|
|
35560
|
+
},
|
|
35523
35561
|
stateMachinePath: {
|
|
35524
35562
|
type: "string",
|
|
35525
35563
|
ops: ["read_states", "add_state", "connect_states"],
|
|
@@ -35694,7 +35732,7 @@ var TOOL_SPECS = [
|
|
|
35694
35732
|
projectPath: PROJECT_PATH2,
|
|
35695
35733
|
resourcePath: {
|
|
35696
35734
|
...RESOURCE_PATH,
|
|
35697
|
-
description: `${RESOURCE_FILE} move: a directory too.`
|
|
35735
|
+
description: `${RESOURCE_FILE} move: a directory too. read: also a resource kept inside a scene or resource file, by the path a read answers a property holding it with, such as "res://hero.tscn::Animation_jump".`
|
|
35698
35736
|
},
|
|
35699
35737
|
resourceType: {
|
|
35700
35738
|
type: "string",
|
|
@@ -37548,7 +37586,7 @@ function exportPresetRefusal(projectPath, preset) {
|
|
|
37548
37586
|
try {
|
|
37549
37587
|
text = readFileSync24(join26(projectPath, "export_presets.cfg"), "utf8");
|
|
37550
37588
|
} catch {
|
|
37551
|
-
return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with.
|
|
37589
|
+
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.`;
|
|
37552
37590
|
}
|
|
37553
37591
|
const names = [];
|
|
37554
37592
|
let inPreset = false;
|
|
@@ -37566,9 +37604,9 @@ function exportPresetRefusal(projectPath, preset) {
|
|
|
37566
37604
|
if (names.includes(preset)) {
|
|
37567
37605
|
return null;
|
|
37568
37606
|
}
|
|
37569
|
-
return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with.
|
|
37607
|
+
return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. project_export add_preset adds one.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
|
|
37570
37608
|
}
|
|
37571
|
-
function exportAnswer(ending, asked, before, after) {
|
|
37609
|
+
function exportAnswer(ending, asked, before, after, beside = []) {
|
|
37572
37610
|
const { log } = ending;
|
|
37573
37611
|
const written = after !== null && after !== before;
|
|
37574
37612
|
const notCopied = librariesNotCopied(log.all);
|
|
@@ -37580,6 +37618,7 @@ function exportAnswer(ending, asked, before, after) {
|
|
|
37580
37618
|
failure: ending.failure ?? undefined,
|
|
37581
37619
|
errors: log.count("error"),
|
|
37582
37620
|
warnings: log.count("warning"),
|
|
37621
|
+
...beside.length === 0 ? {} : { alsoWritten: beside },
|
|
37583
37622
|
...notCopied.length === 0 ? {} : {
|
|
37584
37623
|
librariesNotLoaded: notCopied,
|
|
37585
37624
|
extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
|
|
@@ -37602,6 +37641,20 @@ function exportAnswer(ending, asked, before, after) {
|
|
|
37602
37641
|
isError: true
|
|
37603
37642
|
};
|
|
37604
37643
|
}
|
|
37644
|
+
function writtenBeside(before, after, output, within) {
|
|
37645
|
+
return [...after].filter(([name, at]) => name !== output && before.get(name) !== at).map(([name]) => within === "." ? name : `${within}/${name}`).sort();
|
|
37646
|
+
}
|
|
37647
|
+
function filesIn(folder) {
|
|
37648
|
+
const found = new Map;
|
|
37649
|
+
try {
|
|
37650
|
+
for (const entry of readdirSync11(folder, { withFileTypes: true })) {
|
|
37651
|
+
if (entry.isFile()) {
|
|
37652
|
+
found.set(entry.name, statSync12(join26(folder, entry.name)).mtimeMs);
|
|
37653
|
+
}
|
|
37654
|
+
}
|
|
37655
|
+
} catch {}
|
|
37656
|
+
return found;
|
|
37657
|
+
}
|
|
37605
37658
|
function scanWaitAnswer(scanned, started = "game") {
|
|
37606
37659
|
if (scanned.waitedMs === 0) {
|
|
37607
37660
|
return {};
|
|
@@ -37839,6 +37892,9 @@ var PATH_SOLUTIONS = [
|
|
|
37839
37892
|
'Give the path relative to the project, such as "scenes/main.tscn" or "res://scenes/main.tscn"',
|
|
37840
37893
|
"Point projectPath at the project the file belongs to"
|
|
37841
37894
|
];
|
|
37895
|
+
var OUTPUT_PATH_SOLUTIONS = [
|
|
37896
|
+
'Give outputPath inside the project, relative to it, such as "build/game.exe" or "res://build/game.exe"; copy the build elsewhere once it is exported'
|
|
37897
|
+
];
|
|
37842
37898
|
var PROJECT_FILE_ARGUMENTS = [
|
|
37843
37899
|
"scenePath",
|
|
37844
37900
|
"instancePath",
|
|
@@ -38893,6 +38949,9 @@ class GodotServer {
|
|
|
38893
38949
|
if (tool === "project_import" && op === "set_options") {
|
|
38894
38950
|
return await this.handleSetImportOptions(args);
|
|
38895
38951
|
}
|
|
38952
|
+
if (tool === "project_export" && op === "add_preset") {
|
|
38953
|
+
return await this.handleAddExportPreset(args);
|
|
38954
|
+
}
|
|
38896
38955
|
const headless = HEADLESS_OPERATIONS[tool]?.[op];
|
|
38897
38956
|
if (headless !== undefined) {
|
|
38898
38957
|
const { from: asked, ...answerable } = args;
|
|
@@ -38950,7 +39009,11 @@ class GodotServer {
|
|
|
38950
39009
|
case "scene_create":
|
|
38951
39010
|
return op === "create" ? await bridge("create_scene") : await bridge("save_scene");
|
|
38952
39011
|
case "scene_tree":
|
|
38953
|
-
return await this.readFromFile(args, "scenePath", "read_scene_tree",
|
|
39012
|
+
return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
|
|
39013
|
+
scenePath: file,
|
|
39014
|
+
depth: readNumber(args, "depth") ?? -1,
|
|
39015
|
+
includeProperties: readBoolean(args, "includeProperties") ?? false
|
|
39016
|
+
}), async () => await bridge("list_scene_nodes"));
|
|
38954
39017
|
case "scene_node":
|
|
38955
39018
|
switch (op) {
|
|
38956
39019
|
case "add":
|
|
@@ -38980,9 +39043,17 @@ class GodotServer {
|
|
|
38980
39043
|
case "scene_animation":
|
|
38981
39044
|
switch (op) {
|
|
38982
39045
|
case "read":
|
|
38983
|
-
return await
|
|
39046
|
+
return await this.readFromFile(args, "scenePath", "read_animations", (file) => ({
|
|
39047
|
+
scenePath: file,
|
|
39048
|
+
playerNodePath: readNonEmptyString(args, "playerNodePath") ?? ".",
|
|
39049
|
+
animationName: readString(args, "animationName") ?? ""
|
|
39050
|
+
}), async () => await bridge("read_animations"));
|
|
38984
39051
|
case "read_states":
|
|
38985
|
-
return await
|
|
39052
|
+
return await this.readFromFile(args, "scenePath", "read_animation_states", (file) => ({
|
|
39053
|
+
scenePath: file,
|
|
39054
|
+
animTreePath: readString(args, "animTreePath") ?? "",
|
|
39055
|
+
stateMachinePath: readString(args, "stateMachinePath") ?? ""
|
|
39056
|
+
}), async () => await bridge("read_animation_states"));
|
|
38986
39057
|
case "create":
|
|
38987
39058
|
return await bridge("create_animation");
|
|
38988
39059
|
case "add_track":
|
|
@@ -39009,8 +39080,14 @@ class GodotServer {
|
|
|
39009
39080
|
switch (op) {
|
|
39010
39081
|
case "move":
|
|
39011
39082
|
return await this.handleMoveFile(args);
|
|
39012
|
-
case "read":
|
|
39013
|
-
|
|
39083
|
+
case "read": {
|
|
39084
|
+
const { file, inner } = keptInside(readString(args, "resourcePath") ?? "");
|
|
39085
|
+
const kept = inner === undefined ? {} : { resourcePath: file, subResource: inner };
|
|
39086
|
+
return await this.readFromFile({ ...args, ...kept }, "resourcePath", "read_resource", (found) => ({
|
|
39087
|
+
resourcePath: inner === undefined ? found : `${found}::${inner}`,
|
|
39088
|
+
includeDefaults: readBoolean(args, "includeDefaults") ?? false
|
|
39089
|
+
}), async () => await bridge("read_resource", kept));
|
|
39090
|
+
}
|
|
39014
39091
|
case "create":
|
|
39015
39092
|
return await bridge("create_resource");
|
|
39016
39093
|
case "modify":
|
|
@@ -39938,6 +40015,30 @@ class GodotServer {
|
|
|
39938
40015
|
return this.createErrorResponse(`Search failed: ${errorMessage(error)}`);
|
|
39939
40016
|
}
|
|
39940
40017
|
}
|
|
40018
|
+
async handleAddExportPreset(args) {
|
|
40019
|
+
const project = this.project(args);
|
|
40020
|
+
if (!project.ok) {
|
|
40021
|
+
return project.response;
|
|
40022
|
+
}
|
|
40023
|
+
const given = readNonEmptyString(args, "outputPath");
|
|
40024
|
+
const output = given === undefined ? null : resolveWithinProject(project.value.path, given);
|
|
40025
|
+
if (output !== null && !output.ok) {
|
|
40026
|
+
return this.createErrorResponse(output.reason, OUTPUT_PATH_SOLUTIONS);
|
|
40027
|
+
}
|
|
40028
|
+
const outcome = await this.operation("add_export_preset", {
|
|
40029
|
+
preset: readString(args, "preset") ?? "",
|
|
40030
|
+
platform: readString(args, "platform") ?? "",
|
|
40031
|
+
outputPath: output?.relativePath ?? ""
|
|
40032
|
+
}, project.value.path);
|
|
40033
|
+
if (!outcome.ok || !this.editorHasOpen(project.value.path)) {
|
|
40034
|
+
return this.answer(outcome);
|
|
40035
|
+
}
|
|
40036
|
+
return this.jsonTextResponse({
|
|
40037
|
+
...outcome.payload,
|
|
40038
|
+
editorNote: "The editor open on this project read export_presets.cfg when it started and does not list this preset, and saving presets in its Export dialog writes its own list over the file. editor_launch restart loads the file; an export through project_export run reads the file and needs no restart.",
|
|
40039
|
+
...engineExtras(outcome)
|
|
40040
|
+
});
|
|
40041
|
+
}
|
|
39941
40042
|
async handleExportProject(args) {
|
|
39942
40043
|
const project = this.project(args);
|
|
39943
40044
|
if (!project.ok) {
|
|
@@ -39946,11 +40047,14 @@ class GodotServer {
|
|
|
39946
40047
|
const preset = readNonEmptyString(args, "preset") ?? "";
|
|
39947
40048
|
const output = resolveWithinProject(project.value.path, readString(args, "outputPath") ?? "");
|
|
39948
40049
|
if (!output.ok) {
|
|
39949
|
-
return this.createErrorResponse(output.reason,
|
|
40050
|
+
return this.createErrorResponse(output.reason, OUTPUT_PATH_SOLUTIONS);
|
|
39950
40051
|
}
|
|
39951
40052
|
const noPreset = exportPresetRefusal(project.value.path, preset);
|
|
39952
40053
|
if (noPreset !== null) {
|
|
39953
|
-
return this.createErrorResponse(noPreset, [
|
|
40054
|
+
return this.createErrorResponse(noPreset, [
|
|
40055
|
+
"project_export list reads the presets the project has",
|
|
40056
|
+
"project_export add_preset adds one"
|
|
40057
|
+
]);
|
|
39954
40058
|
}
|
|
39955
40059
|
const engine = await this.engine();
|
|
39956
40060
|
if (!engine.ok) {
|
|
@@ -39977,6 +40081,7 @@ class GodotServer {
|
|
|
39977
40081
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
39978
40082
|
const writtenAt = () => existsSync20(output.absolutePath) ? statSync12(output.absolutePath).mtimeMs : null;
|
|
39979
40083
|
const before = writtenAt();
|
|
40084
|
+
const listedBefore = filesIn(folder);
|
|
39980
40085
|
const freed = freeLibraryCopies(project.value.path);
|
|
39981
40086
|
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
39982
40087
|
this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
|
|
@@ -39990,7 +40095,8 @@ class GodotServer {
|
|
|
39990
40095
|
if (ending.exitSignal === null && ending.exitCode === null) {
|
|
39991
40096
|
return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
|
|
39992
40097
|
}
|
|
39993
|
-
|
|
40098
|
+
const beside = writtenBeside(listedBefore, filesIn(folder), basename7(output.absolutePath), dirname18(output.relativePath).replaceAll("\\", "/"));
|
|
40099
|
+
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside);
|
|
39994
40100
|
}
|
|
39995
40101
|
reportTestProgress(log) {
|
|
39996
40102
|
const progress = callProgress();
|
|
@@ -43400,7 +43506,7 @@ class GodotServer {
|
|
|
43400
43506
|
...leftChanged === undefined ? {} : { note: leftChanged }
|
|
43401
43507
|
});
|
|
43402
43508
|
}
|
|
43403
|
-
async readFromFile(args, pathKey, operation, throughTheEditor) {
|
|
43509
|
+
async readFromFile(args, pathKey, operation, paramsFor, throughTheEditor) {
|
|
43404
43510
|
const project = this.project(args);
|
|
43405
43511
|
if (!project.ok) {
|
|
43406
43512
|
return project.response;
|
|
@@ -43418,12 +43524,7 @@ class GodotServer {
|
|
|
43418
43524
|
if (file === null) {
|
|
43419
43525
|
return this.createErrorResponse(`${pathKey} must name a file inside the project.`);
|
|
43420
43526
|
}
|
|
43421
|
-
const
|
|
43422
|
-
scenePath: file,
|
|
43423
|
-
depth: readNumber(args, "depth") ?? -1,
|
|
43424
|
-
includeProperties: readBoolean(args, "includeProperties") ?? false
|
|
43425
|
-
} : { resourcePath: file, includeDefaults: readBoolean(args, "includeDefaults") ?? false };
|
|
43426
|
-
const outcome = await this.operation(operation, params, projectPath);
|
|
43527
|
+
const outcome = await this.operation(operation, paramsFor(file), projectPath);
|
|
43427
43528
|
if (!outcome.ok) {
|
|
43428
43529
|
return this.answer(outcome);
|
|
43429
43530
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.35",
|
|
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",
|