gdharness 1.1.41 → 1.1.42
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 +54 -10
- package/build/index.js +54 -10
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -17592,8 +17592,9 @@ var init_tool_definitions = __esm(() => {
|
|
|
17592
17592
|
parameters: {
|
|
17593
17593
|
projectPath: PROJECT_PATH,
|
|
17594
17594
|
path: {
|
|
17595
|
-
type: "string",
|
|
17596
|
-
|
|
17595
|
+
type: ["string", "array"],
|
|
17596
|
+
items: { type: "string" },
|
|
17597
|
+
description: "A test directory or suite file inside the project, or a list of them, all run on one engine: a named set of suites boots once rather than once per suite. An entry given twice, or a suite inside a directory also listed, runs once. A run where any entry is not there is refused before the engine starts, naming each one. Default test."
|
|
17597
17598
|
},
|
|
17598
17599
|
ignore: {
|
|
17599
17600
|
type: "array",
|
|
@@ -42085,6 +42086,36 @@ function elsewhereIn(projectPath, asked) {
|
|
|
42085
42086
|
const there = near.map((name) => `res://${name}`).join(" and ");
|
|
42086
42087
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
42087
42088
|
}
|
|
42089
|
+
function testPathsIn(projectPath, given) {
|
|
42090
|
+
const entries = given === undefined || given === null || given === "" ? ["test"] : given;
|
|
42091
|
+
const listed = Array.isArray(entries) ? entries : [entries];
|
|
42092
|
+
if (listed.length === 0) {
|
|
42093
|
+
return {
|
|
42094
|
+
ok: false,
|
|
42095
|
+
reason: "path is an empty list: name a suite file or a directory, or leave path out for test."
|
|
42096
|
+
};
|
|
42097
|
+
}
|
|
42098
|
+
const resolved = [];
|
|
42099
|
+
for (const [index, entry] of listed.entries()) {
|
|
42100
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
42101
|
+
return {
|
|
42102
|
+
ok: false,
|
|
42103
|
+
reason: `path${Array.isArray(entries) ? `[${index}]` : ""} is ${typeof entry === "string" ? "empty" : describeValue(entry)}, not a suite file or a directory in the project.`
|
|
42104
|
+
};
|
|
42105
|
+
}
|
|
42106
|
+
const location = resolveWithinProject(projectPath, entry);
|
|
42107
|
+
if (!location.ok) {
|
|
42108
|
+
return { ok: false, reason: location.reason };
|
|
42109
|
+
}
|
|
42110
|
+
resolved.push(location.relativePath.replace(/\/+$/, ""));
|
|
42111
|
+
}
|
|
42112
|
+
const unique = [...new Set(resolved)];
|
|
42113
|
+
const within = (path, directory) => path.startsWith(`${directory}/`);
|
|
42114
|
+
return {
|
|
42115
|
+
ok: true,
|
|
42116
|
+
value: unique.filter((path) => !unique.some((other) => other !== path && within(path, other))).map((path) => `res://${path}`)
|
|
42117
|
+
};
|
|
42118
|
+
}
|
|
42088
42119
|
function hasMainScene(projectFile) {
|
|
42089
42120
|
const scene = parseProjectGodot(readFileSync21(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
42090
42121
|
return typeof scene === "string" && scene !== "";
|
|
@@ -43600,9 +43631,24 @@ class GodotServer {
|
|
|
43600
43631
|
if (!project.ok) {
|
|
43601
43632
|
return project.response;
|
|
43602
43633
|
}
|
|
43603
|
-
const
|
|
43604
|
-
if (!
|
|
43605
|
-
return
|
|
43634
|
+
const paths = testPathsIn(project.value.path, args["path"]);
|
|
43635
|
+
if (!paths.ok) {
|
|
43636
|
+
return this.createErrorResponse(paths.reason, PATH_SOLUTIONS);
|
|
43637
|
+
}
|
|
43638
|
+
const asked = paths.value;
|
|
43639
|
+
const missing = asked.filter((path) => !existsSync19(join24(project.value.path, path.slice("res://".length))));
|
|
43640
|
+
if (missing.length > 0) {
|
|
43641
|
+
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
43642
|
+
return {
|
|
43643
|
+
content: [
|
|
43644
|
+
{
|
|
43645
|
+
type: "text",
|
|
43646
|
+
text: `No tests ran: ${verdict}.${elsewhereIn(project.value.path, missing[0] ?? "")}`
|
|
43647
|
+
},
|
|
43648
|
+
{ type: "text", text: answerJson({ passed: false, verdict, tests: 0 }) }
|
|
43649
|
+
],
|
|
43650
|
+
isError: true
|
|
43651
|
+
};
|
|
43606
43652
|
}
|
|
43607
43653
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
43608
43654
|
if (!existsSync19(join24(project.value.path, runner))) {
|
|
@@ -43622,7 +43668,6 @@ class GodotServer {
|
|
|
43622
43668
|
const ours = `gdharness-reports/${randomUUID2()}`;
|
|
43623
43669
|
const reports = `res://.godot/${ours}`;
|
|
43624
43670
|
const ignored = readStringArray(args, "ignore") ?? [];
|
|
43625
|
-
const asked = readString(contained.value, "path") ?? "res://test";
|
|
43626
43671
|
const cmdArgs = [
|
|
43627
43672
|
"--headless",
|
|
43628
43673
|
"--path",
|
|
@@ -43631,8 +43676,7 @@ class GodotServer {
|
|
|
43631
43676
|
`res://${runner}`,
|
|
43632
43677
|
"--ignoreHeadlessMode",
|
|
43633
43678
|
...readBoolean(args, "failFast") === true ? [] : ["-c"],
|
|
43634
|
-
"-a",
|
|
43635
|
-
asked,
|
|
43679
|
+
...asked.flatMap((path) => ["-a", path]),
|
|
43636
43680
|
...ignored.flatMap((entry) => ["-i", entry]),
|
|
43637
43681
|
"-rd",
|
|
43638
43682
|
reports,
|
|
@@ -43705,7 +43749,7 @@ class GodotServer {
|
|
|
43705
43749
|
limit: Number.POSITIVE_INFINITY
|
|
43706
43750
|
}).entries;
|
|
43707
43751
|
const said = printed.map((entry) => entry.text);
|
|
43708
|
-
const nothingRan = timedOut ? null : whyNoReport(said, asked);
|
|
43752
|
+
const nothingRan = timedOut ? null : whyNoReport(said, asked.join(" and "));
|
|
43709
43753
|
const cutShort = timedOut ? timedOutVerdict(timeoutMs, silentForMs) : null;
|
|
43710
43754
|
const hung = cutShort?.hung ?? false;
|
|
43711
43755
|
const scriptErrors = timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
|
|
@@ -43719,7 +43763,7 @@ class GodotServer {
|
|
|
43719
43763
|
...echoed.omitted > 0 ? { printedOmitted: echoed.omitted } : {}
|
|
43720
43764
|
};
|
|
43721
43765
|
if (report === null) {
|
|
43722
|
-
const note = nothingRan !== null ? `No tests ran: ${verdict}
|
|
43766
|
+
const note = nothingRan !== null ? `No tests ran: ${verdict}.` : scriptErrors.length > 0 ? scriptErrorsNote(scriptErrors) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${verdict}.` : `The test run wrote no report (${verdict}).`;
|
|
43723
43767
|
return {
|
|
43724
43768
|
content: [
|
|
43725
43769
|
{ type: "text", text: note },
|
package/build/index.js
CHANGED
|
@@ -33013,8 +33013,9 @@ var TOOL_SPECS = [
|
|
|
33013
33013
|
parameters: {
|
|
33014
33014
|
projectPath: PROJECT_PATH,
|
|
33015
33015
|
path: {
|
|
33016
|
-
type: "string",
|
|
33017
|
-
|
|
33016
|
+
type: ["string", "array"],
|
|
33017
|
+
items: { type: "string" },
|
|
33018
|
+
description: "A test directory or suite file inside the project, or a list of them, all run on one engine: a named set of suites boots once rather than once per suite. An entry given twice, or a suite inside a directory also listed, runs once. A run where any entry is not there is refused before the engine starts, naming each one. Default test."
|
|
33018
33019
|
},
|
|
33019
33020
|
ignore: {
|
|
33020
33021
|
type: "array",
|
|
@@ -34659,6 +34660,36 @@ function elsewhereIn(projectPath, asked) {
|
|
|
34659
34660
|
const there = near.map((name) => `res://${name}`).join(" and ");
|
|
34660
34661
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
34661
34662
|
}
|
|
34663
|
+
function testPathsIn(projectPath, given) {
|
|
34664
|
+
const entries = given === undefined || given === null || given === "" ? ["test"] : given;
|
|
34665
|
+
const listed = Array.isArray(entries) ? entries : [entries];
|
|
34666
|
+
if (listed.length === 0) {
|
|
34667
|
+
return {
|
|
34668
|
+
ok: false,
|
|
34669
|
+
reason: "path is an empty list: name a suite file or a directory, or leave path out for test."
|
|
34670
|
+
};
|
|
34671
|
+
}
|
|
34672
|
+
const resolved = [];
|
|
34673
|
+
for (const [index, entry] of listed.entries()) {
|
|
34674
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
34675
|
+
return {
|
|
34676
|
+
ok: false,
|
|
34677
|
+
reason: `path${Array.isArray(entries) ? `[${index}]` : ""} is ${typeof entry === "string" ? "empty" : describeValue(entry)}, not a suite file or a directory in the project.`
|
|
34678
|
+
};
|
|
34679
|
+
}
|
|
34680
|
+
const location = resolveWithinProject(projectPath, entry);
|
|
34681
|
+
if (!location.ok) {
|
|
34682
|
+
return { ok: false, reason: location.reason };
|
|
34683
|
+
}
|
|
34684
|
+
resolved.push(location.relativePath.replace(/\/+$/, ""));
|
|
34685
|
+
}
|
|
34686
|
+
const unique = [...new Set(resolved)];
|
|
34687
|
+
const within = (path, directory) => path.startsWith(`${directory}/`);
|
|
34688
|
+
return {
|
|
34689
|
+
ok: true,
|
|
34690
|
+
value: unique.filter((path) => !unique.some((other) => other !== path && within(path, other))).map((path) => `res://${path}`)
|
|
34691
|
+
};
|
|
34692
|
+
}
|
|
34662
34693
|
function hasMainScene(projectFile) {
|
|
34663
34694
|
const scene = parseProjectGodot(readFileSync19(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
34664
34695
|
return typeof scene === "string" && scene !== "";
|
|
@@ -36174,9 +36205,24 @@ class GodotServer {
|
|
|
36174
36205
|
if (!project.ok) {
|
|
36175
36206
|
return project.response;
|
|
36176
36207
|
}
|
|
36177
|
-
const
|
|
36178
|
-
if (!
|
|
36179
|
-
return
|
|
36208
|
+
const paths = testPathsIn(project.value.path, args["path"]);
|
|
36209
|
+
if (!paths.ok) {
|
|
36210
|
+
return this.createErrorResponse(paths.reason, PATH_SOLUTIONS);
|
|
36211
|
+
}
|
|
36212
|
+
const asked = paths.value;
|
|
36213
|
+
const missing = asked.filter((path) => !existsSync16(join20(project.value.path, path.slice("res://".length))));
|
|
36214
|
+
if (missing.length > 0) {
|
|
36215
|
+
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
36216
|
+
return {
|
|
36217
|
+
content: [
|
|
36218
|
+
{
|
|
36219
|
+
type: "text",
|
|
36220
|
+
text: `No tests ran: ${verdict}.${elsewhereIn(project.value.path, missing[0] ?? "")}`
|
|
36221
|
+
},
|
|
36222
|
+
{ type: "text", text: answerJson({ passed: false, verdict, tests: 0 }) }
|
|
36223
|
+
],
|
|
36224
|
+
isError: true
|
|
36225
|
+
};
|
|
36180
36226
|
}
|
|
36181
36227
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
36182
36228
|
if (!existsSync16(join20(project.value.path, runner))) {
|
|
@@ -36196,7 +36242,6 @@ class GodotServer {
|
|
|
36196
36242
|
const ours = `gdharness-reports/${randomUUID2()}`;
|
|
36197
36243
|
const reports = `res://.godot/${ours}`;
|
|
36198
36244
|
const ignored = readStringArray(args, "ignore") ?? [];
|
|
36199
|
-
const asked = readString(contained.value, "path") ?? "res://test";
|
|
36200
36245
|
const cmdArgs = [
|
|
36201
36246
|
"--headless",
|
|
36202
36247
|
"--path",
|
|
@@ -36205,8 +36250,7 @@ class GodotServer {
|
|
|
36205
36250
|
`res://${runner}`,
|
|
36206
36251
|
"--ignoreHeadlessMode",
|
|
36207
36252
|
...readBoolean(args, "failFast") === true ? [] : ["-c"],
|
|
36208
|
-
"-a",
|
|
36209
|
-
asked,
|
|
36253
|
+
...asked.flatMap((path) => ["-a", path]),
|
|
36210
36254
|
...ignored.flatMap((entry) => ["-i", entry]),
|
|
36211
36255
|
"-rd",
|
|
36212
36256
|
reports,
|
|
@@ -36279,7 +36323,7 @@ class GodotServer {
|
|
|
36279
36323
|
limit: Number.POSITIVE_INFINITY
|
|
36280
36324
|
}).entries;
|
|
36281
36325
|
const said = printed.map((entry) => entry.text);
|
|
36282
|
-
const nothingRan = timedOut ? null : whyNoReport(said, asked);
|
|
36326
|
+
const nothingRan = timedOut ? null : whyNoReport(said, asked.join(" and "));
|
|
36283
36327
|
const cutShort = timedOut ? timedOutVerdict(timeoutMs, silentForMs) : null;
|
|
36284
36328
|
const hung = cutShort?.hung ?? false;
|
|
36285
36329
|
const scriptErrors = timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
|
|
@@ -36293,7 +36337,7 @@ class GodotServer {
|
|
|
36293
36337
|
...echoed.omitted > 0 ? { printedOmitted: echoed.omitted } : {}
|
|
36294
36338
|
};
|
|
36295
36339
|
if (report === null) {
|
|
36296
|
-
const note = nothingRan !== null ? `No tests ran: ${verdict}
|
|
36340
|
+
const note = nothingRan !== null ? `No tests ran: ${verdict}.` : scriptErrors.length > 0 ? scriptErrorsNote(scriptErrors) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${verdict}.` : `The test run wrote no report (${verdict}).`;
|
|
36297
36341
|
return {
|
|
36298
36342
|
content: [
|
|
36299
36343
|
{ type: "text", text: note },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.42",
|
|
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",
|