gdharness 1.2.38 → 1.2.40
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 +1339 -1102
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +7 -4
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +10 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +8 -4
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +15 -2
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +175 -0
- package/build/godot/addons/gdharness_runtime/runtime_items.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +43 -10
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +50 -14
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +48 -18
- package/build/index.js +4893 -4653
- package/build/keeper.js +1 -0
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -8577,9 +8577,616 @@ var init_scratch = __esm(() => {
|
|
|
8577
8577
|
];
|
|
8578
8578
|
});
|
|
8579
8579
|
|
|
8580
|
+
// src/junit.ts
|
|
8581
|
+
function decoded(text) {
|
|
8582
|
+
return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, entity) => {
|
|
8583
|
+
if (entity.startsWith("#")) {
|
|
8584
|
+
const code = entity.startsWith("#x") ? Number.parseInt(entity.slice(2), 16) : Number.parseInt(entity.slice(1), 10);
|
|
8585
|
+
if (!Number.isInteger(code) || code < 0 || code > 1114111) {
|
|
8586
|
+
throw new MalformedReportError(`Not a character: ${whole}`);
|
|
8587
|
+
}
|
|
8588
|
+
return String.fromCodePoint(code);
|
|
8589
|
+
}
|
|
8590
|
+
const named = ENTITIES[entity];
|
|
8591
|
+
if (named === undefined) {
|
|
8592
|
+
throw new MalformedReportError(`Unknown entity ${whole}`);
|
|
8593
|
+
}
|
|
8594
|
+
return named;
|
|
8595
|
+
});
|
|
8596
|
+
}
|
|
8597
|
+
function tagEnd(source, from) {
|
|
8598
|
+
let quote = null;
|
|
8599
|
+
for (let at = from + 1;at < source.length; at += 1) {
|
|
8600
|
+
const character = source[at];
|
|
8601
|
+
if (quote !== null) {
|
|
8602
|
+
if (character === quote) {
|
|
8603
|
+
quote = null;
|
|
8604
|
+
}
|
|
8605
|
+
} else if (character === '"' || character === "'") {
|
|
8606
|
+
quote = character;
|
|
8607
|
+
} else if (character === ">") {
|
|
8608
|
+
return at;
|
|
8609
|
+
}
|
|
8610
|
+
}
|
|
8611
|
+
return -1;
|
|
8612
|
+
}
|
|
8613
|
+
function parseXml(source) {
|
|
8614
|
+
const root = { name: "", attributes: {}, children: [], text: "" };
|
|
8615
|
+
const open = [root];
|
|
8616
|
+
let at = 0;
|
|
8617
|
+
const current = () => {
|
|
8618
|
+
const element = open.at(-1);
|
|
8619
|
+
if (element === undefined) {
|
|
8620
|
+
throw new MalformedReportError("A close tag with nothing open");
|
|
8621
|
+
}
|
|
8622
|
+
return element;
|
|
8623
|
+
};
|
|
8624
|
+
while (at < source.length) {
|
|
8625
|
+
const next = source.indexOf("<", at);
|
|
8626
|
+
if (next === -1) {
|
|
8627
|
+
current().text += decoded(source.slice(at));
|
|
8628
|
+
break;
|
|
8629
|
+
}
|
|
8630
|
+
current().text += decoded(source.slice(at, next));
|
|
8631
|
+
at = next;
|
|
8632
|
+
if (source.startsWith("<![CDATA[", at)) {
|
|
8633
|
+
const end = source.indexOf("]]>", at);
|
|
8634
|
+
if (end === -1) {
|
|
8635
|
+
throw new MalformedReportError("CDATA that never ends");
|
|
8636
|
+
}
|
|
8637
|
+
current().text += source.slice(at + "<![CDATA[".length, end);
|
|
8638
|
+
at = end + "]]>".length;
|
|
8639
|
+
continue;
|
|
8640
|
+
}
|
|
8641
|
+
if (source.startsWith("<!--", at)) {
|
|
8642
|
+
const end = source.indexOf("-->", at);
|
|
8643
|
+
if (end === -1) {
|
|
8644
|
+
throw new MalformedReportError("A comment that never ends");
|
|
8645
|
+
}
|
|
8646
|
+
at = end + "-->".length;
|
|
8647
|
+
continue;
|
|
8648
|
+
}
|
|
8649
|
+
if (source.startsWith("<?", at) || source.startsWith("<!", at)) {
|
|
8650
|
+
const end = source.indexOf(">", at);
|
|
8651
|
+
if (end === -1) {
|
|
8652
|
+
throw new MalformedReportError("A declaration that never ends");
|
|
8653
|
+
}
|
|
8654
|
+
at = end + 1;
|
|
8655
|
+
continue;
|
|
8656
|
+
}
|
|
8657
|
+
const end = tagEnd(source, at);
|
|
8658
|
+
if (end === -1) {
|
|
8659
|
+
throw new MalformedReportError("A tag that never ends");
|
|
8660
|
+
}
|
|
8661
|
+
const tag = source.slice(at + 1, end);
|
|
8662
|
+
at = end + 1;
|
|
8663
|
+
if (tag.startsWith("/")) {
|
|
8664
|
+
const closing = tag.slice(1).trim();
|
|
8665
|
+
const element = current();
|
|
8666
|
+
if (element === root || element.name !== closing) {
|
|
8667
|
+
throw new MalformedReportError(`</${closing}> closes <${element.name || "nothing"}>`);
|
|
8668
|
+
}
|
|
8669
|
+
open.pop();
|
|
8670
|
+
continue;
|
|
8671
|
+
}
|
|
8672
|
+
const selfClosing = tag.endsWith("/");
|
|
8673
|
+
const body = selfClosing ? tag.slice(0, -1) : tag;
|
|
8674
|
+
const nameMatch = /^([^\s/>]+)/.exec(body);
|
|
8675
|
+
if (!nameMatch?.[1]) {
|
|
8676
|
+
throw new MalformedReportError(`A tag with no name: <${tag}>`);
|
|
8677
|
+
}
|
|
8678
|
+
const attributes = {};
|
|
8679
|
+
let read = nameMatch[0].length;
|
|
8680
|
+
ATTRIBUTE.lastIndex = read;
|
|
8681
|
+
let attribute = ATTRIBUTE.exec(body);
|
|
8682
|
+
while (attribute) {
|
|
8683
|
+
attributes[attribute[1] ?? ""] = decoded(attribute[2] ?? attribute[3] ?? "");
|
|
8684
|
+
read = ATTRIBUTE.lastIndex;
|
|
8685
|
+
attribute = ATTRIBUTE.exec(body);
|
|
8686
|
+
}
|
|
8687
|
+
if (body.slice(read).trim() !== "") {
|
|
8688
|
+
throw new MalformedReportError(`Unreadable attributes in <${tag}>`);
|
|
8689
|
+
}
|
|
8690
|
+
const element = { name: nameMatch[1], attributes, children: [], text: "" };
|
|
8691
|
+
current().children.push(element);
|
|
8692
|
+
if (!selfClosing) {
|
|
8693
|
+
open.push(element);
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
if (open.length !== 1) {
|
|
8697
|
+
throw new MalformedReportError(`<${current().name}> is never closed`);
|
|
8698
|
+
}
|
|
8699
|
+
return root;
|
|
8700
|
+
}
|
|
8701
|
+
function count(attributes, name) {
|
|
8702
|
+
const value = Number(attributes[name] ?? "0");
|
|
8703
|
+
return Number.isFinite(value) ? value : 0;
|
|
8704
|
+
}
|
|
8705
|
+
function ignoredYetRan(report, ignored) {
|
|
8706
|
+
const found = [];
|
|
8707
|
+
for (const entry of ignored) {
|
|
8708
|
+
const bare = entry.replace("res://", "");
|
|
8709
|
+
const at = bare.lastIndexOf(":");
|
|
8710
|
+
const suiteNamed = at === -1 ? null : bare.slice(0, at);
|
|
8711
|
+
const testNamed = at === -1 ? bare : bare.slice(at + 1);
|
|
8712
|
+
for (const suite of report.suites) {
|
|
8713
|
+
const suiteMatches = suiteNamed === null || suite.name === suiteNamed || (suite.path ?? "").includes(suiteNamed);
|
|
8714
|
+
const ran = suiteMatches ? suite.cases.find((one) => one.name === testNamed) : undefined;
|
|
8715
|
+
if (ran !== undefined) {
|
|
8716
|
+
found.push({ entry, ran: `${suite.name}:${ran.name}` });
|
|
8717
|
+
}
|
|
8718
|
+
}
|
|
8719
|
+
}
|
|
8720
|
+
return found;
|
|
8721
|
+
}
|
|
8722
|
+
function caseOf(suite, element) {
|
|
8723
|
+
const of = (kind) => element.children.filter((child) => child.name === kind).map((child) => {
|
|
8724
|
+
const detail = child.text.trim();
|
|
8725
|
+
return { kind, message: child.attributes["message"] ?? null, detail: detail === "" ? null : detail };
|
|
8726
|
+
});
|
|
8727
|
+
const reports = [...of("failure"), ...of("error"), ...of("skipped")];
|
|
8728
|
+
const status = reports.some((report) => report.kind === "error") ? "error" : reports.some((report) => report.kind === "failure") ? "failed" : reports.length > 0 ? "skipped" : "passed";
|
|
8729
|
+
return {
|
|
8730
|
+
suite: suite.attributes["name"] ?? "",
|
|
8731
|
+
name: element.attributes["name"] ?? "",
|
|
8732
|
+
status,
|
|
8733
|
+
time: count(element.attributes, "time"),
|
|
8734
|
+
message: reports[0]?.message ?? null,
|
|
8735
|
+
detail: reports[0]?.detail ?? null,
|
|
8736
|
+
reports
|
|
8737
|
+
};
|
|
8738
|
+
}
|
|
8739
|
+
function failedAnAssertion(entry) {
|
|
8740
|
+
return entry.reports.some((report) => report.kind === "failure");
|
|
8741
|
+
}
|
|
8742
|
+
function pathOf(suite) {
|
|
8743
|
+
const directory = suite.attributes["package"];
|
|
8744
|
+
const name = suite.attributes["name"];
|
|
8745
|
+
return directory && name ? `res://${directory}/${name}.gd` : null;
|
|
8746
|
+
}
|
|
8747
|
+
function parseJUnit(xml) {
|
|
8748
|
+
const document = parseXml(xml);
|
|
8749
|
+
const top = document.children.find((child) => child.name === "testsuites" || child.name === "testsuite");
|
|
8750
|
+
if (top === undefined) {
|
|
8751
|
+
throw new MalformedReportError("No <testsuites> or <testsuite> in the report");
|
|
8752
|
+
}
|
|
8753
|
+
const suiteElements = top.name === "testsuite" ? [top] : top.children.filter((child) => child.name === "testsuite");
|
|
8754
|
+
const suites = suiteElements.map((suite) => {
|
|
8755
|
+
const cases = suite.children.filter((child) => child.name === "testcase").map((element) => caseOf(suite, element));
|
|
8756
|
+
const failures = cases.filter(failedAnAssertion).length;
|
|
8757
|
+
const errors = cases.filter((entry) => entry.status === "error").length;
|
|
8758
|
+
const written = (kind) => suite.children.filter((child) => child.name === "testcase").reduce((sum, element) => sum + element.children.filter((child) => child.name === kind).length, 0);
|
|
8759
|
+
return {
|
|
8760
|
+
name: suite.attributes["name"] ?? "",
|
|
8761
|
+
path: pathOf(suite),
|
|
8762
|
+
tests: cases.length,
|
|
8763
|
+
discovered: count(suite.attributes, "tests"),
|
|
8764
|
+
failures,
|
|
8765
|
+
errors,
|
|
8766
|
+
hookFailures: Math.max(0, count(suite.attributes, "failures") - written("failure")),
|
|
8767
|
+
hookErrors: Math.max(0, count(suite.attributes, "errors") - written("error")),
|
|
8768
|
+
skipped: cases.filter((entry) => entry.status === "skipped").length,
|
|
8769
|
+
time: count(suite.attributes, "time"),
|
|
8770
|
+
cases
|
|
8771
|
+
};
|
|
8772
|
+
});
|
|
8773
|
+
const all = suites.flatMap((suite) => suite.cases);
|
|
8774
|
+
return {
|
|
8775
|
+
tests: all.length,
|
|
8776
|
+
failures: all.filter(failedAnAssertion).length,
|
|
8777
|
+
errors: all.filter((entry) => entry.status === "error").length,
|
|
8778
|
+
hookFailures: suites.reduce((sum, suite) => sum + suite.hookFailures, 0),
|
|
8779
|
+
hookErrors: suites.reduce((sum, suite) => sum + suite.hookErrors, 0),
|
|
8780
|
+
skipped: all.filter((entry) => entry.status === "skipped").length,
|
|
8781
|
+
time: suites.reduce((sum, suite) => sum + suite.time, 0),
|
|
8782
|
+
suites
|
|
8783
|
+
};
|
|
8784
|
+
}
|
|
8785
|
+
function hookFailuresPrinted(printed) {
|
|
8786
|
+
return printed.flatMap((line) => {
|
|
8787
|
+
const found = HOOK_FAILURE.exec(line);
|
|
8788
|
+
return found === null ? [] : [{ hook: found[2] ?? "", path: found[3] ?? "", line: Number(found[4]), message: found[1] ?? "" }];
|
|
8789
|
+
});
|
|
8790
|
+
}
|
|
8791
|
+
function whyNoReport(printed, asked) {
|
|
8792
|
+
const missing = printed.map((line) => NO_SUCH_PATH.exec(line)).find((found) => found !== null);
|
|
8793
|
+
if (missing !== undefined) {
|
|
8794
|
+
return `nothing at ${missing[1] ?? asked}`;
|
|
8795
|
+
}
|
|
8796
|
+
return printed.some((line) => NOTHING_RAN.test(line)) ? `no test cases found at ${asked}` : null;
|
|
8797
|
+
}
|
|
8798
|
+
function scriptErrorsPrinted(printed) {
|
|
8799
|
+
return discoveryListing(printed)?.errors ?? [];
|
|
8800
|
+
}
|
|
8801
|
+
function discoveryListing(printed) {
|
|
8802
|
+
const start = printed.findIndex((line) => line.includes(DISCOVERY_FAILED));
|
|
8803
|
+
if (start < 0) {
|
|
8804
|
+
return null;
|
|
8805
|
+
}
|
|
8806
|
+
const found = [];
|
|
8807
|
+
let message = null;
|
|
8808
|
+
let to = start + 1;
|
|
8809
|
+
for (const line of printed.slice(start + 1)) {
|
|
8810
|
+
const at = /^\s*at (res:\/\/.+):(\d+)\s*$/.exec(line);
|
|
8811
|
+
if (at !== null && message !== null) {
|
|
8812
|
+
found.push({ path: at[1] ?? "", line: Number(at[2]) === 0 ? null : Number(at[2]), message });
|
|
8813
|
+
message = null;
|
|
8814
|
+
} else if (at === null && message === null && /^ {2}\S/.test(line)) {
|
|
8815
|
+
message = line.trim();
|
|
8816
|
+
} else {
|
|
8817
|
+
break;
|
|
8818
|
+
}
|
|
8819
|
+
to += 1;
|
|
8820
|
+
}
|
|
8821
|
+
const errors = found.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : (a.line ?? 0) - (b.line ?? 0));
|
|
8822
|
+
return { errors, from: start, to };
|
|
8823
|
+
}
|
|
8824
|
+
function unspelled(text) {
|
|
8825
|
+
return text.replace(/<(?:LF|CR|TAB|BS|FF|VT|BEL|ESC|DEL|0x[0-9A-F]{2})>/g, (spelling) => spelling.startsWith("<0x") ? String.fromCharCode(Number.parseInt(spelling.slice(3, 5), 16)) : SPELLED[spelling] ?? spelling);
|
|
8826
|
+
}
|
|
8827
|
+
function readDiff(printed) {
|
|
8828
|
+
let merged = "";
|
|
8829
|
+
let actual = "";
|
|
8830
|
+
let background = null;
|
|
8831
|
+
let last = 0;
|
|
8832
|
+
const take = (text) => {
|
|
8833
|
+
merged += text;
|
|
8834
|
+
if (background === null) {
|
|
8835
|
+
actual += text;
|
|
8836
|
+
} else if (background === "added") {
|
|
8837
|
+
actual += unspelled(text);
|
|
8838
|
+
}
|
|
8839
|
+
};
|
|
8840
|
+
for (const match of printed.matchAll(COLOUR)) {
|
|
8841
|
+
take(printed.slice(last, match.index));
|
|
8842
|
+
last = match.index + match[0].length;
|
|
8843
|
+
const codes = (match[1] ?? "").split(";");
|
|
8844
|
+
if (codes[0] === "48" && codes[1] === "2") {
|
|
8845
|
+
background = Number(codes[2]) > Number(codes[3]) ? "missing" : "added";
|
|
8846
|
+
} else if (codes[0] === "0" || codes[0] === "") {
|
|
8847
|
+
background = null;
|
|
8848
|
+
}
|
|
8849
|
+
}
|
|
8850
|
+
take(printed.slice(last));
|
|
8851
|
+
return { merged, actual };
|
|
8852
|
+
}
|
|
8853
|
+
function withActualsPrinted(failed, printed) {
|
|
8854
|
+
const diffs = [...printed.replaceAll(`\r
|
|
8855
|
+
`, `
|
|
8856
|
+
`).matchAll(PRINTED_ACTUAL)].map((match) => ({
|
|
8857
|
+
suffix: match[3] ?? "",
|
|
8858
|
+
at: match[5] ?? "",
|
|
8859
|
+
...readDiff(match[1] ?? "")
|
|
8860
|
+
}));
|
|
8861
|
+
const used = new Set;
|
|
8862
|
+
return failed.map((entry) => {
|
|
8863
|
+
if (entry.detail === null) {
|
|
8864
|
+
return entry;
|
|
8865
|
+
}
|
|
8866
|
+
let detail = entry.detail;
|
|
8867
|
+
let recovered = false;
|
|
8868
|
+
for (const [index, diff] of diffs.entries()) {
|
|
8869
|
+
if (used.has(index)) {
|
|
8870
|
+
continue;
|
|
8871
|
+
}
|
|
8872
|
+
const wrong = ` but was
|
|
8873
|
+
'${diff.merged.replaceAll("\r", "")}'${diff.suffix}
|
|
8874
|
+
at ${diff.at}`;
|
|
8875
|
+
if (detail.includes(wrong)) {
|
|
8876
|
+
detail = detail.replace(wrong, () => ` but was
|
|
8877
|
+
'${diff.actual}'${diff.suffix}
|
|
8878
|
+
at ${diff.at}`);
|
|
8879
|
+
recovered = true;
|
|
8880
|
+
used.add(index);
|
|
8881
|
+
}
|
|
8882
|
+
}
|
|
8883
|
+
const quoted = QUOTED_EQUALITY.exec(detail);
|
|
8884
|
+
if (quoted !== null) {
|
|
8885
|
+
const alike = quoted[1] === quoted[2];
|
|
8886
|
+
const note = recovered ? alike ? RENDERED_AWAY_NOTE : null : alike ? BOTH_SIDES_ALIKE_NOTE : VALUE_NOT_RECOVERED_NOTE;
|
|
8887
|
+
detail = note === null ? detail : `${detail}
|
|
8888
|
+
${note}`;
|
|
8889
|
+
}
|
|
8890
|
+
if (quoted !== null && masked(quoted[1] ?? "", quoted[2] ?? "")) {
|
|
8891
|
+
const end = quoted[0].length - `
|
|
8892
|
+
at `.length;
|
|
8893
|
+
detail = detail.slice(0, end).replaceAll(MASKED_BRACKET, "[") + detail.slice(end);
|
|
8894
|
+
}
|
|
8895
|
+
return detail === entry.detail ? entry : { ...entry, detail };
|
|
8896
|
+
});
|
|
8897
|
+
}
|
|
8898
|
+
function shortened(detail) {
|
|
8899
|
+
if (detail === null || detail.length <= LONGEST_DETAIL) {
|
|
8900
|
+
return detail;
|
|
8901
|
+
}
|
|
8902
|
+
return `${detail.slice(0, LONGEST_DETAIL)}
|
|
8903
|
+
[${detail.length - LONGEST_DETAIL} more characters left out]`;
|
|
8904
|
+
}
|
|
8905
|
+
function foldedReports(reports) {
|
|
8906
|
+
const first = new Map;
|
|
8907
|
+
for (const report of reports) {
|
|
8908
|
+
const key = JSON.stringify([report.kind, report.message, report.detail]);
|
|
8909
|
+
const seen = first.get(key);
|
|
8910
|
+
if (seen === undefined) {
|
|
8911
|
+
first.set(key, { report, times: 1 });
|
|
8912
|
+
} else {
|
|
8913
|
+
seen.times += 1;
|
|
8914
|
+
}
|
|
8915
|
+
}
|
|
8916
|
+
return [...first.values()].map(({ report, times }) => {
|
|
8917
|
+
const kept = { ...report, detail: shortened(report.detail) };
|
|
8918
|
+
return times === 1 ? kept : { ...kept, times };
|
|
8919
|
+
});
|
|
8920
|
+
}
|
|
8921
|
+
function failedCases(report, printed) {
|
|
8922
|
+
const cases = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ entry, path: suite.path })));
|
|
8923
|
+
const read = withActualsPrinted(cases.flatMap(({ entry }) => entry.reports), printed);
|
|
8924
|
+
let next = 0;
|
|
8925
|
+
return cases.map(({ entry, path }) => {
|
|
8926
|
+
const reports = read.slice(next, next + entry.reports.length);
|
|
8927
|
+
next += entry.reports.length;
|
|
8928
|
+
const folded = foldedReports(reports);
|
|
8929
|
+
const shown = folded.slice(0, MOST_REPORTS_SHOWN);
|
|
8930
|
+
return {
|
|
8931
|
+
suite: entry.suite,
|
|
8932
|
+
name: entry.name,
|
|
8933
|
+
status: entry.status,
|
|
8934
|
+
time: entry.time,
|
|
8935
|
+
path,
|
|
8936
|
+
message: reports[0]?.message ?? null,
|
|
8937
|
+
detail: shortened(reports[0]?.detail ?? null),
|
|
8938
|
+
...reports.length > 1 ? { reports: shown, reportCount: reports.length } : {},
|
|
8939
|
+
...folded.length > shown.length ? { reportsLeftOut: folded.length - shown.length } : {}
|
|
8940
|
+
};
|
|
8941
|
+
});
|
|
8942
|
+
}
|
|
8943
|
+
function failedWithin(failed, budget) {
|
|
8944
|
+
const sizes = failed.map((entry) => JSON.stringify(entry).length);
|
|
8945
|
+
const smallestFirst = sizes.map((_, index) => index).sort((a, b) => (sizes[a] ?? 0) - (sizes[b] ?? 0));
|
|
8946
|
+
const whole = new Set;
|
|
8947
|
+
let spent = 0;
|
|
8948
|
+
for (const index of smallestFirst) {
|
|
8949
|
+
const size = sizes[index] ?? 0;
|
|
8950
|
+
if (spent + size > budget) {
|
|
8951
|
+
break;
|
|
8952
|
+
}
|
|
8953
|
+
spent += size;
|
|
8954
|
+
whole.add(index);
|
|
8955
|
+
}
|
|
8956
|
+
const kept = failed.map((entry, index) => {
|
|
8957
|
+
if (whole.has(index)) {
|
|
8958
|
+
return entry;
|
|
8959
|
+
}
|
|
8960
|
+
const { detail: _detail, reports: _reports, reportsLeftOut: _left, ...named } = entry;
|
|
8961
|
+
return { ...named, message: entry.message?.slice(0, 500) ?? null, detail: null };
|
|
8962
|
+
});
|
|
8963
|
+
return { failed: kept, briefed: failed.length - whole.size };
|
|
8964
|
+
}
|
|
8965
|
+
function masked(expected, actual) {
|
|
8966
|
+
const allSpelled = (value) => !PRINTED_DICTIONARY.test(value) && value.split("[").length === value.split(MASKED_BRACKET).length;
|
|
8967
|
+
return `${expected}${actual}`.includes(MASKED_BRACKET) && allSpelled(expected) && allSpelled(actual);
|
|
8968
|
+
}
|
|
8969
|
+
function orphansPrinted(printed) {
|
|
8970
|
+
const suites = [];
|
|
8971
|
+
let total = null;
|
|
8972
|
+
let running = null;
|
|
8973
|
+
for (const line of printed) {
|
|
8974
|
+
const started = RUNNING_SUITE.exec(line);
|
|
8975
|
+
if (started?.[1] !== undefined) {
|
|
8976
|
+
running = started[1];
|
|
8977
|
+
continue;
|
|
8978
|
+
}
|
|
8979
|
+
const counted = ORPHANS.exec(line);
|
|
8980
|
+
if (counted?.[1] === undefined) {
|
|
8981
|
+
continue;
|
|
8982
|
+
}
|
|
8983
|
+
const orphans = Number(counted[1]);
|
|
8984
|
+
if (line.includes(OVERALL)) {
|
|
8985
|
+
total = orphans;
|
|
8986
|
+
running = null;
|
|
8987
|
+
continue;
|
|
8988
|
+
}
|
|
8989
|
+
if (running !== null && orphans > 0) {
|
|
8990
|
+
suites.push({ path: running, orphans });
|
|
8991
|
+
}
|
|
8992
|
+
running = null;
|
|
8993
|
+
}
|
|
8994
|
+
return {
|
|
8995
|
+
total: total ?? suites.reduce((sum, suite) => sum + suite.orphans, 0),
|
|
8996
|
+
suites
|
|
8997
|
+
};
|
|
8998
|
+
}
|
|
8999
|
+
function finishesASuite(line) {
|
|
9000
|
+
return SUITE_COUNTS.test(line) && !line.includes(OVERALL);
|
|
9001
|
+
}
|
|
9002
|
+
function suitesPrinted(printed) {
|
|
9003
|
+
const finished = [];
|
|
9004
|
+
let running = null;
|
|
9005
|
+
let failed = [];
|
|
9006
|
+
for (const line of printed) {
|
|
9007
|
+
const started = RUNNING_SUITE.exec(line);
|
|
9008
|
+
if (started?.[1] !== undefined) {
|
|
9009
|
+
running = started[1];
|
|
9010
|
+
failed = [];
|
|
9011
|
+
continue;
|
|
9012
|
+
}
|
|
9013
|
+
if (running === null) {
|
|
9014
|
+
continue;
|
|
9015
|
+
}
|
|
9016
|
+
const failing = CASE_FAILED.exec(line)?.[1];
|
|
9017
|
+
if (failing !== undefined) {
|
|
9018
|
+
failed.push(failing);
|
|
9019
|
+
continue;
|
|
9020
|
+
}
|
|
9021
|
+
const counted = finishesASuite(line) ? SUITE_COUNTS.exec(line) : null;
|
|
9022
|
+
if (counted !== null) {
|
|
9023
|
+
finished.push({
|
|
9024
|
+
path: running,
|
|
9025
|
+
cases: Number(counted[1]),
|
|
9026
|
+
errors: Number(counted[2]),
|
|
9027
|
+
failures: Number(counted[3]),
|
|
9028
|
+
failed
|
|
9029
|
+
});
|
|
9030
|
+
running = null;
|
|
9031
|
+
}
|
|
9032
|
+
}
|
|
9033
|
+
return { finished, running };
|
|
9034
|
+
}
|
|
9035
|
+
function testProgressMessage(printed, elapsedMs) {
|
|
9036
|
+
const { finished, running } = suitesPrinted(printed);
|
|
9037
|
+
const failing = finished.filter((suite) => suite.errors + suite.failures > 0).length;
|
|
9038
|
+
const seconds = Math.floor(elapsedMs / 1000);
|
|
9039
|
+
const took = seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
9040
|
+
const done = finished.length === 0 ? "No suite has finished" : `${finished.length === 1 ? "One suite has" : `${finished.length} suites have`} finished${failing === 0 ? "" : `, ${failing} with failures`}`;
|
|
9041
|
+
return `${done}${running === null ? "" : `; running ${running}`}; ${took} in.`;
|
|
9042
|
+
}
|
|
9043
|
+
var MalformedReportError, ENTITIES, ATTRIBUTE, HOOK_FAILURE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MOST_REPORTS_SHOWN = 20, LONGEST_DETAIL = 20000, MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary", SUITE_COUNTS, CASE_FAILED;
|
|
9044
|
+
var init_junit = __esm(() => {
|
|
9045
|
+
MalformedReportError = class MalformedReportError extends Error {
|
|
9046
|
+
constructor(message) {
|
|
9047
|
+
super(message);
|
|
9048
|
+
this.name = "MalformedReportError";
|
|
9049
|
+
}
|
|
9050
|
+
};
|
|
9051
|
+
ENTITIES = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
|
|
9052
|
+
ATTRIBUTE = /\s*([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/y;
|
|
9053
|
+
HOOK_FAILURE = /^\s*(.*?)\s*\tat '(before|after)' in (res:\/\/.+?):(\d+)\s*$/;
|
|
9054
|
+
NOTHING_RAN = /no test cases found/i;
|
|
9055
|
+
NO_SUCH_PATH = /given directory or file does not exists:\s*(.+?)\s*$/i;
|
|
9056
|
+
ESCAPE = String.fromCharCode(27);
|
|
9057
|
+
PRINTED_ACTUAL = new RegExp(` but was\\n '((?:(?!\\tat ${LOCATION})[\\s\\S])*?)'((?:${ESCAPE}\\[[0-9;]*m)*)(${IGNORING_CASE})?((?:${ESCAPE}\\[[0-9;]*m)*)\\tat (${LOCATION})`, "g");
|
|
9058
|
+
QUOTED_EQUALITY = new RegExp(`^Expecting(?: error message)?:\\n '([\\s\\S]*)'\\n but was\\n '([\\s\\S]*)'(?:${IGNORING_CASE})?\\n\\tat `);
|
|
9059
|
+
COLOUR = new RegExp(`${ESCAPE}\\[([0-9;]*)m`, "g");
|
|
9060
|
+
SPELLED = {
|
|
9061
|
+
"<LF>": `
|
|
9062
|
+
`,
|
|
9063
|
+
"<CR>": "\r",
|
|
9064
|
+
"<TAB>": "\t",
|
|
9065
|
+
"<BS>": "\b",
|
|
9066
|
+
"<FF>": "\f",
|
|
9067
|
+
"<VT>": "\v",
|
|
9068
|
+
"<BEL>": "\x07",
|
|
9069
|
+
"<ESC>": ESCAPE,
|
|
9070
|
+
"<DEL>": ""
|
|
9071
|
+
};
|
|
9072
|
+
PRINTED_DICTIONARY = /^\{\n\t[\s\S]*\n {2}\}$/;
|
|
9073
|
+
RUNNING_SUITE = /Run Test Suite:\s*(res:\/\/.+?\.(?:gd|cs))(?=\s|$)/;
|
|
9074
|
+
ORPHANS = /(\d+)\s+orphans/;
|
|
9075
|
+
SUITE_COUNTS = /(\d+)\s+test cases\s*\|\s*(\d+)\s+errors\s*\|\s*(\d+)\s+failures/;
|
|
9076
|
+
CASE_FAILED = /^\s*res:\/\/.+?\.(?:gd|cs) > (.+?) (?:FAILED|ERROR)(?:\s|$)/;
|
|
9077
|
+
});
|
|
9078
|
+
|
|
9079
|
+
// src/script-origins.ts
|
|
9080
|
+
import { join as join9 } from "node:path";
|
|
9081
|
+
function unresolvedClassIn(message) {
|
|
9082
|
+
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
9083
|
+
}
|
|
9084
|
+
async function originsOf(errors, declared, check) {
|
|
9085
|
+
const listed = new Set(errors.map((error) => error.path));
|
|
9086
|
+
const origins = [];
|
|
9087
|
+
const unconfirmed = [];
|
|
9088
|
+
const asked = new Set;
|
|
9089
|
+
const waiting = errors.map((error) => unresolvedClassIn(error.message)).filter((name) => name !== null);
|
|
9090
|
+
while (waiting.length > 0 && asked.size < MOST_CHECKED) {
|
|
9091
|
+
const name = waiting.shift() ?? "";
|
|
9092
|
+
const path = declared.get(name);
|
|
9093
|
+
if (path === undefined || listed.has(path) || asked.has(path)) {
|
|
9094
|
+
continue;
|
|
9095
|
+
}
|
|
9096
|
+
asked.add(path);
|
|
9097
|
+
const checked = await check(path);
|
|
9098
|
+
const own = checked.errors.filter((error) => error.path === path);
|
|
9099
|
+
if (own.length === 0) {
|
|
9100
|
+
unconfirmed.push({
|
|
9101
|
+
path,
|
|
9102
|
+
declares: name,
|
|
9103
|
+
why: checked.failure === null ? "the engine's check of it found no error" : `the engine's check of it did not finish (${checked.failure})`
|
|
9104
|
+
});
|
|
9105
|
+
continue;
|
|
9106
|
+
}
|
|
9107
|
+
for (const error of own) {
|
|
9108
|
+
origins.push({ ...error, declares: name });
|
|
9109
|
+
const deeper = unresolvedClassIn(error.message);
|
|
9110
|
+
if (deeper !== null) {
|
|
9111
|
+
waiting.push(deeper);
|
|
9112
|
+
}
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
return { origins, unconfirmed };
|
|
9116
|
+
}
|
|
9117
|
+
function notCompiledIn(entry) {
|
|
9118
|
+
return NOT_COMPILED.exec(entry.text)?.[1];
|
|
9119
|
+
}
|
|
9120
|
+
function compileErrorsIn(entries) {
|
|
9121
|
+
return entries.flatMap((entry) => {
|
|
9122
|
+
const at = entry.severity === "error" ? reloadedAt(entry) : null;
|
|
9123
|
+
return at === null ? [] : [{ ...at, message: entry.text }];
|
|
9124
|
+
});
|
|
9125
|
+
}
|
|
9126
|
+
function reloadedAt(entry) {
|
|
9127
|
+
for (const line of entry.detail) {
|
|
9128
|
+
const at = RELOADED_AT.exec(line.trim());
|
|
9129
|
+
if (at !== null) {
|
|
9130
|
+
return { path: at[1] ?? "", line: Number(at[2]) === 0 ? null : Number(at[2]) };
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
return null;
|
|
9134
|
+
}
|
|
9135
|
+
function checkWithEngine(godotPath, projectPath) {
|
|
9136
|
+
return async (path) => {
|
|
9137
|
+
const logDir = scratchDirectory("tests");
|
|
9138
|
+
try {
|
|
9139
|
+
const ran = await runEngine(godotPath, [
|
|
9140
|
+
"--headless",
|
|
9141
|
+
"--log-file",
|
|
9142
|
+
join9(logDir, "engine.log"),
|
|
9143
|
+
"--path",
|
|
9144
|
+
projectPath,
|
|
9145
|
+
"--check-only",
|
|
9146
|
+
"-s",
|
|
9147
|
+
path
|
|
9148
|
+
], { timeout: { ms: 60000, said: "minute" } });
|
|
9149
|
+
const errors = ran.log.select({ severity: "error", sinceLastCall: false, limit: 50 }).entries.flatMap((entry) => {
|
|
9150
|
+
const at = reloadedAt(entry);
|
|
9151
|
+
return at === null ? [] : [{ ...at, message: entry.text }];
|
|
9152
|
+
});
|
|
9153
|
+
return { errors, failure: ran.failure === null ? null : howItEnded(ran) };
|
|
9154
|
+
} finally {
|
|
9155
|
+
discard(logDir);
|
|
9156
|
+
}
|
|
9157
|
+
};
|
|
9158
|
+
}
|
|
9159
|
+
function withoutListed(entries, errors) {
|
|
9160
|
+
if (errors.length === 0) {
|
|
9161
|
+
return [...entries];
|
|
9162
|
+
}
|
|
9163
|
+
const listing = discoveryListing(entries.map((entry) => entry.text));
|
|
9164
|
+
const paths = new Set(errors.map((error) => error.path));
|
|
9165
|
+
return entries.filter((entry, at) => {
|
|
9166
|
+
if (listing !== null && at >= listing.from && at < listing.to) {
|
|
9167
|
+
return false;
|
|
9168
|
+
}
|
|
9169
|
+
const reloaded = reloadedAt(entry);
|
|
9170
|
+
if (reloaded !== null && errors.some((error) => error.path === reloaded.path && error.line === reloaded.line && error.message === entry.text)) {
|
|
9171
|
+
return false;
|
|
9172
|
+
}
|
|
9173
|
+
const failedToLoad = /^Failed to load script "(res:\/\/[^"]+)" with error "[^"]+"\.$/.exec(entry.text);
|
|
9174
|
+
return failedToLoad === null || !paths.has(failedToLoad[1] ?? "");
|
|
9175
|
+
});
|
|
9176
|
+
}
|
|
9177
|
+
var UNRESOLVED, MOST_CHECKED = 8, RELOADED_AT, NOT_COMPILED;
|
|
9178
|
+
var init_script_origins = __esm(() => {
|
|
9179
|
+
init_engine_run();
|
|
9180
|
+
init_junit();
|
|
9181
|
+
init_scratch();
|
|
9182
|
+
UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
|
|
9183
|
+
RELOADED_AT = /GDScript::reload \((res:\/\/.+):(\d+)\)$/;
|
|
9184
|
+
NOT_COMPILED = /^Failed to load script "(res:\/\/[^"]+)" with error "(?:Parse error|Compilation failed)"/;
|
|
9185
|
+
});
|
|
9186
|
+
|
|
8580
9187
|
// src/headless.ts
|
|
8581
9188
|
import { existsSync as existsSync9, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8582
|
-
import { dirname as dirname6, join as
|
|
9189
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
8583
9190
|
function snakeCased(params) {
|
|
8584
9191
|
const result = emptyRecord();
|
|
8585
9192
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -8595,15 +9202,22 @@ function engineExtras(outcome) {
|
|
|
8595
9202
|
...outcome.messages.length > 0 ? { engine_messages: outcome.messages } : {},
|
|
8596
9203
|
...before.length > 0 ? {
|
|
8597
9204
|
engine_messages_before_operation: before,
|
|
8598
|
-
engine_messages_before_operation_note: BEFORE_OPERATION_NOTE
|
|
9205
|
+
engine_messages_before_operation_note: withNotCompiled(BEFORE_OPERATION_NOTE, before)
|
|
8599
9206
|
} : {},
|
|
8600
9207
|
...outcome.beforeOperationOmitted === undefined ? {} : { engine_messages_before_operation_omitted: outcome.beforeOperationOmitted },
|
|
8601
9208
|
...outcome.messagesOmitted === undefined ? {} : { engine_messages_omitted: outcome.messagesOmitted },
|
|
8602
|
-
...after.length > 0 ? {
|
|
9209
|
+
...after.length > 0 ? {
|
|
9210
|
+
engine_messages_after_answer: after,
|
|
9211
|
+
engine_messages_after_answer_note: withNotCompiled(AFTER_ANSWER_NOTE, after)
|
|
9212
|
+
} : {},
|
|
8603
9213
|
...outcome.afterAnswerOmitted === undefined ? {} : { engine_messages_after_answer_omitted: outcome.afterAnswerOmitted },
|
|
8604
9214
|
...outcome.afterAnswer === undefined ? {} : { engine_after_answer: outcome.afterAnswer }
|
|
8605
9215
|
};
|
|
8606
9216
|
}
|
|
9217
|
+
function withNotCompiled(note, entries) {
|
|
9218
|
+
const path = entries.map(notCompiledIn).find((found) => found !== undefined);
|
|
9219
|
+
return path === undefined ? note : `${note} ${path} did not compile, so every run of this project starts its autoloads without it until it is fixed; its errors are among these.`;
|
|
9220
|
+
}
|
|
8607
9221
|
function problemsIn(log) {
|
|
8608
9222
|
return problemsAmong(log.everything());
|
|
8609
9223
|
}
|
|
@@ -8704,7 +9318,7 @@ async function importOnce(godotPath, projectPath) {
|
|
|
8704
9318
|
ran = await runEngine(godotPath, [
|
|
8705
9319
|
"--headless",
|
|
8706
9320
|
"--log-file",
|
|
8707
|
-
|
|
9321
|
+
join10(logDir, "engine.log"),
|
|
8708
9322
|
"--path",
|
|
8709
9323
|
projectPath,
|
|
8710
9324
|
"--import"
|
|
@@ -8726,13 +9340,13 @@ async function importOnce(godotPath, projectPath) {
|
|
|
8726
9340
|
}
|
|
8727
9341
|
async function runOperation(engine, operation, params, projectPath) {
|
|
8728
9342
|
const paramsDir = scratchDirectory("params");
|
|
8729
|
-
const paramsFile =
|
|
8730
|
-
const answerFile =
|
|
9343
|
+
const paramsFile = join10(paramsDir, `${operation}.json`);
|
|
9344
|
+
const answerFile = join10(paramsDir, "answer.json");
|
|
8731
9345
|
writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
8732
9346
|
const args = [
|
|
8733
9347
|
"--headless",
|
|
8734
9348
|
"--log-file",
|
|
8735
|
-
|
|
9349
|
+
join10(paramsDir, "engine.log"),
|
|
8736
9350
|
"--path",
|
|
8737
9351
|
projectPath,
|
|
8738
9352
|
"--script",
|
|
@@ -8841,12 +9455,13 @@ var init_headless = __esm(() => {
|
|
|
8841
9455
|
init_library_copies();
|
|
8842
9456
|
init_reimport();
|
|
8843
9457
|
init_scratch();
|
|
9458
|
+
init_script_origins();
|
|
8844
9459
|
});
|
|
8845
9460
|
|
|
8846
9461
|
// src/update-check.ts
|
|
8847
9462
|
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8848
9463
|
import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
|
|
8849
|
-
import { join as
|
|
9464
|
+
import { join as join11 } from "node:path";
|
|
8850
9465
|
function packageUrl(root) {
|
|
8851
9466
|
return `${root.replace(/\/+$/, "")}/gdharness/latest`;
|
|
8852
9467
|
}
|
|
@@ -8868,20 +9483,20 @@ function cacheDirectory(environment) {
|
|
|
8868
9483
|
};
|
|
8869
9484
|
const home = set("HOME") ?? homedir4();
|
|
8870
9485
|
if (process.platform === "win32") {
|
|
8871
|
-
return
|
|
9486
|
+
return join11(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
8872
9487
|
}
|
|
8873
9488
|
if (process.platform === "darwin") {
|
|
8874
|
-
return
|
|
9489
|
+
return join11(home, "Library", "Caches", "gdharness");
|
|
8875
9490
|
}
|
|
8876
|
-
return
|
|
9491
|
+
return join11(set("XDG_CACHE_HOME") ?? join11(home, ".cache"), "gdharness");
|
|
8877
9492
|
}
|
|
8878
9493
|
function cacheFile(environment = process.env) {
|
|
8879
9494
|
try {
|
|
8880
9495
|
const directory = cacheDirectory(environment);
|
|
8881
9496
|
mkdirSync3(directory, { recursive: true, mode: 448 });
|
|
8882
|
-
return
|
|
9497
|
+
return join11(directory, "update-check.json");
|
|
8883
9498
|
} catch {
|
|
8884
|
-
return
|
|
9499
|
+
return join11(tmpdir3(), "gdharness-update-check.json");
|
|
8885
9500
|
}
|
|
8886
9501
|
}
|
|
8887
9502
|
function readCache(path) {
|
|
@@ -9897,7 +10512,7 @@ var init_gdscript_source = __esm(() => {
|
|
|
9897
10512
|
|
|
9898
10513
|
// src/class-cache.ts
|
|
9899
10514
|
import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "node:fs";
|
|
9900
|
-
import { join as
|
|
10515
|
+
import { join as join12, posix } from "node:path";
|
|
9901
10516
|
function isToolScript(source) {
|
|
9902
10517
|
return TOOL.test(codeOf(source));
|
|
9903
10518
|
}
|
|
@@ -9913,14 +10528,14 @@ function declaredClasses(projectPath) {
|
|
|
9913
10528
|
}
|
|
9914
10529
|
function eachScript(projectPath, each) {
|
|
9915
10530
|
const visit = (directory, prefix) => {
|
|
9916
|
-
if (existsSync11(
|
|
10531
|
+
if (existsSync11(join12(directory, ".gdignore"))) {
|
|
9917
10532
|
return;
|
|
9918
10533
|
}
|
|
9919
10534
|
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
9920
10535
|
if (entry.name.startsWith(".")) {
|
|
9921
10536
|
continue;
|
|
9922
10537
|
}
|
|
9923
|
-
const path =
|
|
10538
|
+
const path = join12(directory, entry.name);
|
|
9924
10539
|
if (entry.isDirectory()) {
|
|
9925
10540
|
visit(path, `${prefix}${entry.name}/`);
|
|
9926
10541
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
@@ -9946,7 +10561,7 @@ function scriptsNaming(projectPath, classes) {
|
|
|
9946
10561
|
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
9947
10562
|
}
|
|
9948
10563
|
function cachedClasses(projectPath) {
|
|
9949
|
-
const cache =
|
|
10564
|
+
const cache = join12(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
9950
10565
|
if (!existsSync11(cache)) {
|
|
9951
10566
|
return null;
|
|
9952
10567
|
}
|
|
@@ -10217,7 +10832,7 @@ function uncachedClassNote(types) {
|
|
|
10217
10832
|
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.";
|
|
10218
10833
|
}
|
|
10219
10834
|
function cacheWrittenAt(projectPath) {
|
|
10220
|
-
const cache =
|
|
10835
|
+
const cache = join12(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
10221
10836
|
try {
|
|
10222
10837
|
return statSync6(cache).mtimeMs;
|
|
10223
10838
|
} catch {
|
|
@@ -10231,14 +10846,14 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
10231
10846
|
return false;
|
|
10232
10847
|
}
|
|
10233
10848
|
try {
|
|
10234
|
-
return statSync6(
|
|
10849
|
+
return statSync6(join12(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
10235
10850
|
} catch {
|
|
10236
10851
|
return false;
|
|
10237
10852
|
}
|
|
10238
10853
|
});
|
|
10239
10854
|
}
|
|
10240
10855
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
10241
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync11(
|
|
10856
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync11(join12(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
10242
10857
|
}
|
|
10243
10858
|
function staleAgainst(cached, projectPath) {
|
|
10244
10859
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -10264,7 +10879,7 @@ var init_class_cache = __esm(() => {
|
|
|
10264
10879
|
|
|
10265
10880
|
// src/project-scan.ts
|
|
10266
10881
|
import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "node:fs";
|
|
10267
|
-
import { extname, join as
|
|
10882
|
+
import { extname, join as join13 } from "node:path";
|
|
10268
10883
|
function skipped(name) {
|
|
10269
10884
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
10270
10885
|
}
|
|
@@ -10272,10 +10887,10 @@ function steppedOver(directory) {
|
|
|
10272
10887
|
return gdignored(directory) || installedByUs(directory);
|
|
10273
10888
|
}
|
|
10274
10889
|
function gdignored(directory) {
|
|
10275
|
-
return existsSync12(
|
|
10890
|
+
return existsSync12(join13(directory, ".gdignore"));
|
|
10276
10891
|
}
|
|
10277
10892
|
function installedByUs(directory) {
|
|
10278
|
-
return existsSync12(
|
|
10893
|
+
return existsSync12(join13(directory, VERSION_MARKER));
|
|
10279
10894
|
}
|
|
10280
10895
|
function projectStructure(projectPath) {
|
|
10281
10896
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -10288,7 +10903,7 @@ function projectStructure(projectPath) {
|
|
|
10288
10903
|
continue;
|
|
10289
10904
|
}
|
|
10290
10905
|
if (entry.isDirectory()) {
|
|
10291
|
-
visit(
|
|
10906
|
+
visit(join13(directory, entry.name));
|
|
10292
10907
|
} else if (entry.isFile()) {
|
|
10293
10908
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
10294
10909
|
if (SCENE_EXTENSIONS.has(extension)) {
|
|
@@ -10318,7 +10933,7 @@ function projectFiles(projectPath, extensions) {
|
|
|
10318
10933
|
}
|
|
10319
10934
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
10320
10935
|
if (entry.isDirectory()) {
|
|
10321
|
-
visit(
|
|
10936
|
+
visit(join13(directory, entry.name), spelled);
|
|
10322
10937
|
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
10323
10938
|
found.push(spelled);
|
|
10324
10939
|
}
|
|
@@ -10339,14 +10954,14 @@ function scriptsWithoutUid(projectPath) {
|
|
|
10339
10954
|
}
|
|
10340
10955
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
10341
10956
|
if (entry.isDirectory()) {
|
|
10342
|
-
visit(
|
|
10957
|
+
visit(join13(directory, entry.name), spelled);
|
|
10343
10958
|
continue;
|
|
10344
10959
|
}
|
|
10345
10960
|
if (!entry.isFile()) {
|
|
10346
10961
|
continue;
|
|
10347
10962
|
}
|
|
10348
10963
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
10349
|
-
if (CARRIES_A_UID.has(extension) && !existsSync12(
|
|
10964
|
+
if (CARRIES_A_UID.has(extension) && !existsSync12(join13(directory, `${entry.name}.uid`))) {
|
|
10350
10965
|
found.push(spelled);
|
|
10351
10966
|
}
|
|
10352
10967
|
}
|
|
@@ -10367,7 +10982,7 @@ function projectTexts(projectPath) {
|
|
|
10367
10982
|
if (skipped(entry.name)) {
|
|
10368
10983
|
continue;
|
|
10369
10984
|
}
|
|
10370
|
-
const absolute =
|
|
10985
|
+
const absolute = join13(directory, entry.name);
|
|
10371
10986
|
const spelled = `${prefix}${entry.name}`;
|
|
10372
10987
|
if (entry.isDirectory()) {
|
|
10373
10988
|
visit(absolute, `${spelled}/`);
|
|
@@ -10397,7 +11012,7 @@ function ignoredTexts(projectPath) {
|
|
|
10397
11012
|
if (skipped(entry.name)) {
|
|
10398
11013
|
continue;
|
|
10399
11014
|
}
|
|
10400
|
-
const absolute =
|
|
11015
|
+
const absolute = join13(directory, entry.name);
|
|
10401
11016
|
const spelled = `${prefix}${entry.name}`;
|
|
10402
11017
|
if (entry.isDirectory()) {
|
|
10403
11018
|
visit(absolute, `${spelled}/`, under);
|
|
@@ -10437,7 +11052,7 @@ function searchProject(projectPath, options) {
|
|
|
10437
11052
|
if (skipped(entry.name)) {
|
|
10438
11053
|
continue;
|
|
10439
11054
|
}
|
|
10440
|
-
const entryPath =
|
|
11055
|
+
const entryPath = join13(directory, entry.name);
|
|
10441
11056
|
if (entry.isDirectory()) {
|
|
10442
11057
|
visit(entryPath);
|
|
10443
11058
|
continue;
|
|
@@ -10891,7 +11506,7 @@ import {
|
|
|
10891
11506
|
statSync as statSync7,
|
|
10892
11507
|
writeFileSync as writeFileSync6
|
|
10893
11508
|
} from "node:fs";
|
|
10894
|
-
import { dirname as dirname7, join as
|
|
11509
|
+
import { dirname as dirname7, join as join14 } from "node:path";
|
|
10895
11510
|
import { fileURLToPath } from "node:url";
|
|
10896
11511
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
10897
11512
|
return addonsDigest(EDITOR_PLUGINS, from);
|
|
@@ -10906,7 +11521,7 @@ function addonsDigest(names, from) {
|
|
|
10906
11521
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
10907
11522
|
continue;
|
|
10908
11523
|
}
|
|
10909
|
-
const path =
|
|
11524
|
+
const path = join14(directory, name);
|
|
10910
11525
|
const inside = `${relative}/${name}`;
|
|
10911
11526
|
if (statSync7(path).isDirectory()) {
|
|
10912
11527
|
walk(path, inside);
|
|
@@ -10920,7 +11535,7 @@ function addonsDigest(names, from) {
|
|
|
10920
11535
|
}
|
|
10921
11536
|
};
|
|
10922
11537
|
for (const name of names) {
|
|
10923
|
-
walk(
|
|
11538
|
+
walk(join14(from, name), name);
|
|
10924
11539
|
}
|
|
10925
11540
|
return hash.digest("hex");
|
|
10926
11541
|
}
|
|
@@ -10951,7 +11566,7 @@ function installedRuntimeDigest(projectPath) {
|
|
|
10951
11566
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
10952
11567
|
}
|
|
10953
11568
|
function digestMarker(projectPath, addon) {
|
|
10954
|
-
const marker =
|
|
11569
|
+
const marker = join14(projectPath, "addons", addon, DIGEST_MARKER);
|
|
10955
11570
|
if (!existsSync13(marker)) {
|
|
10956
11571
|
return null;
|
|
10957
11572
|
}
|
|
@@ -10959,35 +11574,35 @@ function digestMarker(projectPath, addon) {
|
|
|
10959
11574
|
return said === "" ? null : said;
|
|
10960
11575
|
}
|
|
10961
11576
|
function shippedAddonsDirectory() {
|
|
10962
|
-
return
|
|
11577
|
+
return join14(dirname7(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
10963
11578
|
}
|
|
10964
11579
|
function shippedOperationsScript() {
|
|
10965
|
-
return
|
|
11580
|
+
return join14(dirname7(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
10966
11581
|
}
|
|
10967
11582
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
10968
11583
|
const installed = [];
|
|
10969
11584
|
for (const name of ADDONS) {
|
|
10970
|
-
const source =
|
|
10971
|
-
if (!existsSync13(
|
|
11585
|
+
const source = join14(from, name);
|
|
11586
|
+
if (!existsSync13(join14(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
10972
11587
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
10973
11588
|
}
|
|
10974
|
-
const target =
|
|
11589
|
+
const target = join14(projectPath, "addons", name);
|
|
10975
11590
|
const replaced = existsSync13(target);
|
|
10976
11591
|
rmSync5(target, { recursive: true, force: true });
|
|
10977
11592
|
mkdirSync4(dirname7(target), { recursive: true });
|
|
10978
11593
|
cpSync(source, target, { recursive: true });
|
|
10979
|
-
writeFileSync6(
|
|
11594
|
+
writeFileSync6(join14(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
10980
11595
|
`);
|
|
10981
11596
|
installed.push({ name, path: target, replaced });
|
|
10982
11597
|
}
|
|
10983
|
-
writeFileSync6(
|
|
11598
|
+
writeFileSync6(join14(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
10984
11599
|
`);
|
|
10985
|
-
writeFileSync6(
|
|
11600
|
+
writeFileSync6(join14(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
|
|
10986
11601
|
`);
|
|
10987
11602
|
return installed;
|
|
10988
11603
|
}
|
|
10989
11604
|
function installedAddonVersion(projectPath) {
|
|
10990
|
-
const marker =
|
|
11605
|
+
const marker = join14(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
10991
11606
|
if (!existsSync13(marker)) {
|
|
10992
11607
|
return null;
|
|
10993
11608
|
}
|
|
@@ -10997,13 +11612,13 @@ function installedAddonVersion(projectPath) {
|
|
|
10997
11612
|
function removeAddons(projectPath) {
|
|
10998
11613
|
const removed = [];
|
|
10999
11614
|
for (const name of ADDONS) {
|
|
11000
|
-
const target =
|
|
11615
|
+
const target = join14(projectPath, "addons", name);
|
|
11001
11616
|
if (existsSync13(target)) {
|
|
11002
11617
|
rmSync5(target, { recursive: true, force: true });
|
|
11003
11618
|
removed.push(target);
|
|
11004
11619
|
}
|
|
11005
11620
|
}
|
|
11006
|
-
const addons =
|
|
11621
|
+
const addons = join14(projectPath, "addons");
|
|
11007
11622
|
if (removed.length > 0 && existsSync13(addons) && readdirSync8(addons).length === 0) {
|
|
11008
11623
|
rmdirSync(addons);
|
|
11009
11624
|
removed.push(addons);
|
|
@@ -11038,7 +11653,7 @@ function loaderAmong(projectPath, named) {
|
|
|
11038
11653
|
}
|
|
11039
11654
|
let source = null;
|
|
11040
11655
|
try {
|
|
11041
|
-
source = readFileSync11(
|
|
11656
|
+
source = readFileSync11(join14(projectPath, path), "utf8");
|
|
11042
11657
|
} catch {}
|
|
11043
11658
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
11044
11659
|
return { name: entry, path, exists: true };
|
|
@@ -11080,8 +11695,8 @@ function enabledPlugins(settings) {
|
|
|
11080
11695
|
function inspectProject(projectPath) {
|
|
11081
11696
|
const problems = [];
|
|
11082
11697
|
const addons = ADDONS.map((name) => {
|
|
11083
|
-
const directory =
|
|
11084
|
-
const marker =
|
|
11698
|
+
const directory = join14(projectPath, "addons", name);
|
|
11699
|
+
const marker = join14(directory, VERSION_MARKER);
|
|
11085
11700
|
const installed = existsSync13(directory);
|
|
11086
11701
|
const version = existsSync13(marker) ? readFileSync11(marker, "utf8").trim() : null;
|
|
11087
11702
|
const current = version === SERVER_VERSION;
|
|
@@ -11092,7 +11707,7 @@ function inspectProject(projectPath) {
|
|
|
11092
11707
|
}
|
|
11093
11708
|
return { name, installed, version, current };
|
|
11094
11709
|
});
|
|
11095
|
-
const settings = parseProjectGodot(readFileSync11(
|
|
11710
|
+
const settings = parseProjectGodot(readFileSync11(join14(projectPath, "project.godot"), "utf8"));
|
|
11096
11711
|
const pluginsEnabled = enabledPlugins(settings);
|
|
11097
11712
|
for (const name of EDITOR_PLUGINS) {
|
|
11098
11713
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -11108,7 +11723,7 @@ function inspectProject(projectPath) {
|
|
|
11108
11723
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
11109
11724
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
11110
11725
|
for (const [name, path] of named) {
|
|
11111
|
-
if (!existsSync13(
|
|
11726
|
+
if (!existsSync13(join14(projectPath, path))) {
|
|
11112
11727
|
if (!addonMissing(path)) {
|
|
11113
11728
|
problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
|
|
11114
11729
|
}
|
|
@@ -11256,7 +11871,7 @@ function annotationsOf(spec) {
|
|
|
11256
11871
|
openWorldHint: false
|
|
11257
11872
|
};
|
|
11258
11873
|
}
|
|
11259
|
-
var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "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, or readBy for a read. That engine runs every script of the file, 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 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.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS,
|
|
11874
|
+
var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "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, or readBy for a read. That engine runs every script of the file, 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 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.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR2, TOOL_SPECS, SPECS_BY_NAME;
|
|
11260
11875
|
var init_tool_definitions = __esm(() => {
|
|
11261
11876
|
init_launch();
|
|
11262
11877
|
PROJECT_INFO_SECTIONS = [
|
|
@@ -11412,7 +12027,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11412
12027
|
additionalProperties: false
|
|
11413
12028
|
}
|
|
11414
12029
|
};
|
|
11415
|
-
|
|
12030
|
+
COLOUR2 = {
|
|
11416
12031
|
type: "object",
|
|
11417
12032
|
description: "set_theme_color: r, g and b from 0 to 1, with a optional and opaque by default.",
|
|
11418
12033
|
properties: { r: { type: "number" }, g: { type: "number" }, b: { type: "number" }, a: { type: "number" } },
|
|
@@ -11424,7 +12039,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11424
12039
|
name: "project_info",
|
|
11425
12040
|
title: "Project info",
|
|
11426
12041
|
effect: "read",
|
|
11427
|
-
description: "What a project is: its name and main scene from project.godot, the Godot that answers, and how many scenes, scripts and assets it holds, with optional sections on top.",
|
|
12042
|
+
description: "What a project is: its name and main scene from project.godot, the Godot that answers, and how many scenes, scripts and assets it holds, other counting every remaining file, leaving out the addons gdharness installed and any folder holding a .gdignore, with optional sections on top.",
|
|
11428
12043
|
parameters: {
|
|
11429
12044
|
projectPath: PROJECT_PATH,
|
|
11430
12045
|
include: {
|
|
@@ -11666,7 +12281,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11666
12281
|
},
|
|
11667
12282
|
reimport: {
|
|
11668
12283
|
effect: "destructive",
|
|
11669
|
-
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. 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",
|
|
12284
|
+
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",
|
|
11670
12285
|
requires: []
|
|
11671
12286
|
},
|
|
11672
12287
|
uid: {
|
|
@@ -11725,7 +12340,8 @@ var init_tool_definitions = __esm(() => {
|
|
|
11725
12340
|
name: "project_test",
|
|
11726
12341
|
title: "Run tests",
|
|
11727
12342
|
effect: "additive",
|
|
11728
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted
|
|
12343
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
12344
|
+
answer: "time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for.",
|
|
11729
12345
|
parameters: {
|
|
11730
12346
|
projectPath: PROJECT_PATH,
|
|
11731
12347
|
path: {
|
|
@@ -11750,7 +12366,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
11750
12366
|
timeoutMs: {
|
|
11751
12367
|
type: "integer",
|
|
11752
12368
|
minimum: 1,
|
|
11753
|
-
description: "How long the run may take before it is killed. Default 600000. A run killed at the limit answers timedOut and silentForMs, how long it had printed nothing; it is called hung only when that was half the limit, or thirty seconds of a longer one, and otherwise it was still running and wants a longer timeoutMs or a narrower path."
|
|
12369
|
+
description: "How long the run may take before it is killed. Default 600000. A run killed at the limit answers timedOut and silentForMs, how long it had printed nothing; it is called hung only when that was half the limit, or thirty seconds of a longer one, and otherwise it was still running and wants a longer timeoutMs or a narrower path, unless it ended repeating one script error, which the verdict names instead. A run whose autoload did not compile is stopped as soon as the engine says so, and names the script that did not under scriptErrors."
|
|
11754
12370
|
},
|
|
11755
12371
|
printed: {
|
|
11756
12372
|
type: "string",
|
|
@@ -12210,7 +12826,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12210
12826
|
ops: ["set_theme_color"],
|
|
12211
12827
|
description: 'set_theme_color: such as "font_color".'
|
|
12212
12828
|
},
|
|
12213
|
-
color: { ...
|
|
12829
|
+
color: { ...COLOUR2, ops: ["set_theme_color"] },
|
|
12214
12830
|
fontSizeName: {
|
|
12215
12831
|
type: "string",
|
|
12216
12832
|
ops: ["set_theme_font_size"],
|
|
@@ -12421,7 +13037,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12421
13037
|
requires: []
|
|
12422
13038
|
}
|
|
12423
13039
|
},
|
|
12424
|
-
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
|
|
13040
|
+
answer: "start: scene is the scene the game started in, the project's main scene when none was given. refreshedClasses names the class_name declarations on disk that the engine's class cache was missing, which were added to it before the game started so the game could resolve them; it is empty when the cache was current. stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. The runtime addon's own functions, gdharness answering its tools inside the game, are not among them: their self time is runtimeAddonMs, over runtimeAddonFunctions functions. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. frames counts the frames of the profile that arrived, and the engine's totals also hold the work after the last of them, so once complete a function run every frame can show a call more than frames. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
|
|
12425
13041
|
defaultOperation: "start"
|
|
12426
13042
|
},
|
|
12427
13043
|
{
|
|
@@ -12589,7 +13205,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12589
13205
|
property: {
|
|
12590
13206
|
type: "string",
|
|
12591
13207
|
ops: ["property", "find"],
|
|
12592
|
-
description:
|
|
13208
|
+
description: `property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A key holding a colon, a bracket or a quote is written in double quotes, 'stock:"base:dew"', as a refusal listing the keys writes it. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A vector, a colour, a rectangle or a transform is stepped into by its components as GDScript names them, so one axis is one number: "size:x", "global_position:y", "modulate:a", "global_transform:origin:z"; a name the value does not have is refused, naming the ones it has. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control's theme is read where it is drawn: 'get_theme_stylebox("panel"):content_margin_top' or 'get_theme_constant("separation")'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.`
|
|
12593
13209
|
},
|
|
12594
13210
|
depth: {
|
|
12595
13211
|
type: "integer",
|
|
@@ -12809,7 +13425,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12809
13425
|
name: "runtime_input",
|
|
12810
13426
|
title: "Send input to the game",
|
|
12811
13427
|
description: "Input to the running game: a whole click on a Control or a 3D node named by path or on a control named by the words on it, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button, mouse motion or drag. All of it works headless, where the viewport is the project's window size and the GUI only takes what is inside it. On gdharness's own desktop, where a windowed run goes, Windows refuses to say where the pointer is: every pointer answer there carries pointer_on_hidden_desktop, and the first of the run pointer_note saying why it matters: get_mouse_position() on the root window, and the mouse positions derived from it, read a meaningless position, while what reads the event itself (the control under it, hover, tooltips, input handlers) gets the injected position. A native window no real pointer has entered gets no hover from Godot, so the runtime supplies it there and the answers say hover_supplied.",
|
|
12812
|
-
answer: 'click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again, for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. None of these is aimed at anything, so none says what the event reached: click on a nodePath or says answers what was under the pointer.',
|
|
13428
|
+
answer: 'click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, found.item names the tab, list entry, tree row or menu bar title pressed (kind, index where its control numbers them, says, and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again, for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. None of these is aimed at anything, so none says what the event reached: click on a nodePath or says answers what was under the pointer.',
|
|
12813
13429
|
parameters: {
|
|
12814
13430
|
projectPath: RUNNING_PROJECT_PATH,
|
|
12815
13431
|
pid: RUNNING_PID,
|
|
@@ -12821,7 +13437,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
12821
13437
|
says: {
|
|
12822
13438
|
type: "string",
|
|
12823
13439
|
ops: ["click"],
|
|
12824
|
-
description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it, and | separates alternatives), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it or a dialog stacked above its own, is not on screen; nor is one on a hidden canvas layer, one outside the viewport, or one a container clips away that a click does not scroll. The refusal counts those apart. The matches are ranked the way a person picks what to press: an enabled button before anything else, a disabled one included, then a control whose whole text, or one whole line of it such as the title on a card, is the words before one saying them as part of more, and the best one is pressed; found.picked says why. Several matches at the best rank are refused with each one listed, and index picks one. A match saying the words only as part of more is named under found.partOf, and is refused when another control says exactly the words (hidden, off the screen, covered, or on screen taking clicks without being a button), since it would stand in for that one, and when it lets the pointer through to what is under it; index presses it anyway. Words found only inside longer words (\"out\" in \"Scout\") rank after every other match and are refused unless index is given; words starting a longer word (\"Recruit\" in \"Recruits\") count. An empty field says its placeholder, and a control with no text of its own, a picture on a button, says its accessibility name. An item of an open menu is drawn by the menu rather than held as a control, so a click finds none, and the refusal names the menu and the choose call that picks it."
|
|
13440
|
+
description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it, and | separates alternatives), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it or a dialog stacked above its own, is not on screen; nor is one on a hidden canvas layer, one outside the viewport, or one a container clips away that a click does not scroll. The refusal counts those apart. The matches are ranked the way a person picks what to press: an enabled button before anything else, a disabled one included, then a control whose whole text, or one whole line of it such as the title on a card, is the words before one saying them as part of more, and the best one is pressed; found.picked says why. Several matches at the best rank are refused with each one listed, and index picks one. A match saying the words only as part of more is named under found.partOf, and is refused when another control says exactly the words (hidden, off the screen, covered, or on screen taking clicks without being a button), since it would stand in for that one, and when it lets the pointer through to what is under it; index presses it anyway. Words found only inside longer words (\"out\" in \"Scout\") rank after every other match and are refused unless index is given; words starting a longer word (\"Recruit\" in \"Recruits\") count. An empty field says its placeholder, and a control with no text of its own, a picture on a button, says its accessibility name. A tab, a list entry, a tree row and a menu bar title each count as a match of their own and are pressed where they are drawn, after their control scrolls them into view; a disabled one is refused. An item of an open menu is drawn by the menu rather than held as a control, so a click finds none, and the refusal names the menu and the choose call that picks it."
|
|
12825
13441
|
},
|
|
12826
13442
|
action: {
|
|
12827
13443
|
type: "string",
|
|
@@ -34899,11 +35515,50 @@ var init_stdio = __esm(() => {
|
|
|
34899
35515
|
swallowsErrorsAfterClose = Symbol("swallowsErrorsAfterClose");
|
|
34900
35516
|
});
|
|
34901
35517
|
|
|
35518
|
+
// src/autoload-failure.ts
|
|
35519
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
|
|
35520
|
+
import { join as join17 } from "node:path";
|
|
35521
|
+
function scriptsAutoloaded(projectPath) {
|
|
35522
|
+
const scripts = new Map;
|
|
35523
|
+
let settings;
|
|
35524
|
+
try {
|
|
35525
|
+
settings = parseProjectGodot(readFileSync13(join17(projectPath, "project.godot"), "utf8"));
|
|
35526
|
+
} catch {
|
|
35527
|
+
return scripts;
|
|
35528
|
+
}
|
|
35529
|
+
for (const [name, path] of autoloadPaths(settings)) {
|
|
35530
|
+
if (!path.endsWith(".tscn")) {
|
|
35531
|
+
scripts.set(`res://${path}`, name);
|
|
35532
|
+
continue;
|
|
35533
|
+
}
|
|
35534
|
+
const scene = join17(projectPath, path);
|
|
35535
|
+
if (!existsSync15(scene)) {
|
|
35536
|
+
continue;
|
|
35537
|
+
}
|
|
35538
|
+
try {
|
|
35539
|
+
for (const named of readFileSync13(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
|
|
35540
|
+
scripts.set(named[1] ?? "", name);
|
|
35541
|
+
}
|
|
35542
|
+
} catch {}
|
|
35543
|
+
}
|
|
35544
|
+
return scripts;
|
|
35545
|
+
}
|
|
35546
|
+
function autoloadNotCompiled(entry, autoloaded) {
|
|
35547
|
+
const path = notCompiledIn(entry);
|
|
35548
|
+
const name = path === undefined ? undefined : autoloaded.get(path);
|
|
35549
|
+
return path === undefined || name === undefined ? null : { name, path };
|
|
35550
|
+
}
|
|
35551
|
+
var init_autoload_failure = __esm(() => {
|
|
35552
|
+
init_resources();
|
|
35553
|
+
init_script_origins();
|
|
35554
|
+
init_setup();
|
|
35555
|
+
});
|
|
35556
|
+
|
|
34902
35557
|
// src/runtime-client.ts
|
|
34903
|
-
import { existsSync as
|
|
35558
|
+
import { existsSync as existsSync16, readdirSync as readdirSync10, readFileSync as readFileSync14, statSync as statSync8, unlinkSync as unlinkSync2 } from "node:fs";
|
|
34904
35559
|
import { createConnection } from "node:net";
|
|
34905
35560
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
34906
|
-
import { dirname as dirname10, join as
|
|
35561
|
+
import { dirname as dirname10, join as join18, resolve as resolve4 } from "node:path";
|
|
34907
35562
|
import { setTimeout as delay } from "node:timers/promises";
|
|
34908
35563
|
function runtimeDirectory(variables = process.env) {
|
|
34909
35564
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -34911,20 +35566,20 @@ function runtimeDirectory(variables = process.env) {
|
|
|
34911
35566
|
return explicit;
|
|
34912
35567
|
}
|
|
34913
35568
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
34914
|
-
return
|
|
35569
|
+
return join18(perUser ?? tmpdir4(), "gdharness");
|
|
34915
35570
|
}
|
|
34916
35571
|
function runtimeDirectories(variables = process.env) {
|
|
34917
35572
|
const candidates = [runtimeDirectory(variables)];
|
|
34918
35573
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
34919
35574
|
for (const base of fallbacks) {
|
|
34920
|
-
candidates.push(
|
|
35575
|
+
candidates.push(join18(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
34921
35576
|
}
|
|
34922
35577
|
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
34923
35578
|
}
|
|
34924
35579
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
34925
35580
|
for (const directory of directories) {
|
|
34926
|
-
const path =
|
|
34927
|
-
if (
|
|
35581
|
+
const path = join18(directory, `runtime-${pid}.log`);
|
|
35582
|
+
if (existsSync16(path)) {
|
|
34928
35583
|
return path;
|
|
34929
35584
|
}
|
|
34930
35585
|
}
|
|
@@ -34955,7 +35610,7 @@ function reportAlreadyInTranscript(transcript, report) {
|
|
|
34955
35610
|
function parseAnnouncement(file, pid) {
|
|
34956
35611
|
let fields;
|
|
34957
35612
|
try {
|
|
34958
|
-
fields = asParams(JSON.parse(
|
|
35613
|
+
fields = asParams(JSON.parse(readFileSync14(file, "utf8")));
|
|
34959
35614
|
} catch {
|
|
34960
35615
|
return { kind: "rubbish" };
|
|
34961
35616
|
}
|
|
@@ -35021,7 +35676,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
|
|
|
35021
35676
|
function wentAway(file, pid) {
|
|
35022
35677
|
let project = "";
|
|
35023
35678
|
try {
|
|
35024
|
-
const fields = asParams(JSON.parse(
|
|
35679
|
+
const fields = asParams(JSON.parse(readFileSync14(file, "utf8")));
|
|
35025
35680
|
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
35026
35681
|
} catch {
|
|
35027
35682
|
return;
|
|
@@ -35064,7 +35719,7 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
35064
35719
|
}).map((one) => one.file));
|
|
35065
35720
|
}
|
|
35066
35721
|
function announcedIn(directory) {
|
|
35067
|
-
if (!
|
|
35722
|
+
if (!existsSync16(directory)) {
|
|
35068
35723
|
return [];
|
|
35069
35724
|
}
|
|
35070
35725
|
const found = [];
|
|
@@ -35073,14 +35728,14 @@ function announcedIn(directory) {
|
|
|
35073
35728
|
for (const entry of readdirSync10(directory)) {
|
|
35074
35729
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
35075
35730
|
if (report) {
|
|
35076
|
-
sweepErrorReport(
|
|
35731
|
+
sweepErrorReport(join18(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
35077
35732
|
continue;
|
|
35078
35733
|
}
|
|
35079
35734
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
35080
35735
|
if (!match) {
|
|
35081
35736
|
continue;
|
|
35082
35737
|
}
|
|
35083
|
-
const file =
|
|
35738
|
+
const file = join18(directory, entry);
|
|
35084
35739
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
35085
35740
|
const running = alive(pid);
|
|
35086
35741
|
entries.push({ file, pid, running });
|
|
@@ -35108,7 +35763,7 @@ function announcedIn(directory) {
|
|
|
35108
35763
|
found.push(announced);
|
|
35109
35764
|
}
|
|
35110
35765
|
const held = new Set(entries.map((one) => one.file));
|
|
35111
|
-
const here =
|
|
35766
|
+
const here = join18(directory, ".");
|
|
35112
35767
|
for (const file of verdicts.keys()) {
|
|
35113
35768
|
if (dirname10(file) === here && !held.has(file)) {
|
|
35114
35769
|
verdicts.delete(file);
|
|
@@ -35117,7 +35772,7 @@ function announcedIn(directory) {
|
|
|
35117
35772
|
return found;
|
|
35118
35773
|
}
|
|
35119
35774
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
35120
|
-
const files = directories.map((directory) =>
|
|
35775
|
+
const files = directories.map((directory) => join18(directory, `runtime-${pid}.json`)).filter((file) => existsSync16(file));
|
|
35121
35776
|
const first = files[0];
|
|
35122
35777
|
if (first === undefined) {
|
|
35123
35778
|
return;
|
|
@@ -35281,7 +35936,36 @@ function keepLate(id, answer) {
|
|
|
35281
35936
|
function lateAnswer(id) {
|
|
35282
35937
|
return lateAnswers.get(id);
|
|
35283
35938
|
}
|
|
35284
|
-
function
|
|
35939
|
+
function placeOf(endpoint) {
|
|
35940
|
+
return `${dialAddress(endpoint.address)}:${endpoint.port}`;
|
|
35941
|
+
}
|
|
35942
|
+
function waitForAPlace(game, granted) {
|
|
35943
|
+
const line = untaken.get(game) ?? { held: 0, waiting: [] };
|
|
35944
|
+
untaken.set(game, line);
|
|
35945
|
+
const grant = () => {
|
|
35946
|
+
line.held += 1;
|
|
35947
|
+
let given = false;
|
|
35948
|
+
granted(() => {
|
|
35949
|
+
if (given) {
|
|
35950
|
+
return;
|
|
35951
|
+
}
|
|
35952
|
+
given = true;
|
|
35953
|
+
line.held -= 1;
|
|
35954
|
+
const next = line.waiting.shift();
|
|
35955
|
+
if (next !== undefined) {
|
|
35956
|
+
next();
|
|
35957
|
+
} else if (line.held === 0 && untaken.get(game) === line) {
|
|
35958
|
+
untaken.delete(game);
|
|
35959
|
+
}
|
|
35960
|
+
});
|
|
35961
|
+
};
|
|
35962
|
+
if (line.held < UNTAKEN_PER_GAME) {
|
|
35963
|
+
grant();
|
|
35964
|
+
} else {
|
|
35965
|
+
line.waiting.push(grant);
|
|
35966
|
+
}
|
|
35967
|
+
}
|
|
35968
|
+
function runtimeRequest(endpoint, command, params, timeoutMs, afterTheWait = "drop") {
|
|
35285
35969
|
const id = nextRequestId++;
|
|
35286
35970
|
const sentAt = Date.now();
|
|
35287
35971
|
return new Promise((settle) => {
|
|
@@ -35289,16 +35973,32 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
35289
35973
|
let late = false;
|
|
35290
35974
|
let buffered = "";
|
|
35291
35975
|
let welcomed = false;
|
|
35292
|
-
|
|
35976
|
+
let socket = null;
|
|
35977
|
+
let refusals = 0;
|
|
35978
|
+
let redial = null;
|
|
35979
|
+
let giveBackThePlace = null;
|
|
35980
|
+
const taken = () => {
|
|
35981
|
+
giveBackThePlace?.();
|
|
35982
|
+
giveBackThePlace = null;
|
|
35983
|
+
};
|
|
35293
35984
|
let lateTimer = null;
|
|
35294
35985
|
const finish = (reply) => {
|
|
35986
|
+
taken();
|
|
35987
|
+
if (redial !== null) {
|
|
35988
|
+
clearTimeout(redial);
|
|
35989
|
+
redial = null;
|
|
35990
|
+
}
|
|
35295
35991
|
if (late) {
|
|
35296
35992
|
if (lateTimer !== null) {
|
|
35297
35993
|
clearTimeout(lateTimer);
|
|
35298
35994
|
}
|
|
35299
|
-
socket
|
|
35300
|
-
keepLate(id, reply.ok || reply.reason === "error" ? { state: "answered", command, sentAt, answeredAt: Date.now(), reply } : { state: "never", command, sentAt, why: reply.message });
|
|
35995
|
+
socket?.destroy();
|
|
35301
35996
|
late = false;
|
|
35997
|
+
if (typeof afterTheWait === "function") {
|
|
35998
|
+
afterTheWait(reply);
|
|
35999
|
+
return;
|
|
36000
|
+
}
|
|
36001
|
+
keepLate(id, reply.ok || reply.reason === "error" ? { state: "answered", command, sentAt, answeredAt: Date.now(), reply } : { state: "never", command, sentAt, why: reply.message });
|
|
35302
36002
|
return;
|
|
35303
36003
|
}
|
|
35304
36004
|
if (done) {
|
|
@@ -35306,12 +36006,24 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
35306
36006
|
}
|
|
35307
36007
|
done = true;
|
|
35308
36008
|
clearTimeout(timer);
|
|
35309
|
-
socket
|
|
36009
|
+
socket?.destroy();
|
|
35310
36010
|
settle(reply);
|
|
35311
36011
|
};
|
|
35312
36012
|
const timer = setTimeout(() => {
|
|
36013
|
+
if (socket === null) {
|
|
36014
|
+
finish(refusals > 0 ? {
|
|
36015
|
+
ok: false,
|
|
36016
|
+
reason: "refused",
|
|
36017
|
+
message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: it refused ${refusals === 1 ? "the connection" : `all ${refusals} connections tried`} within ${timeoutMs}ms. Nothing listens there while a game is starting or shutting down, and the engine refuses connections while more are waiting for the game than the eight it queues, which several servers calling one game at once, or a long frame or a breakpoint holding it, can make it do. Nothing was sent, so nothing ran.`
|
|
36018
|
+
} : {
|
|
36019
|
+
ok: false,
|
|
36020
|
+
reason: "busy",
|
|
36021
|
+
message: `'${command}' was not sent to the game (${describe2(endpoint)}) within ${timeoutMs}ms: ${UNTAKEN_PER_GAME} connections from this server were already waiting for the game to take them, which it does between frames, so a long frame or a breakpoint holds them. Nothing was sent, so nothing ran.`
|
|
36022
|
+
});
|
|
36023
|
+
return;
|
|
36024
|
+
}
|
|
35313
36025
|
const still = welcomed ? `The game (${describe2(endpoint)}) has the request and did not answer '${command}' within ${timeoutMs}ms.` : `The game (${describe2(endpoint)}) did not answer '${command}' within ${timeoutMs}ms and had not taken the request: its frame has not ended since it was sent, which a long frame or a breakpoint does, and the request runs when it does.`;
|
|
35314
|
-
if (
|
|
36026
|
+
if (afterTheWait === "drop") {
|
|
35315
36027
|
finish({
|
|
35316
36028
|
ok: false,
|
|
35317
36029
|
reason: "busy",
|
|
@@ -35321,7 +36033,9 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
35321
36033
|
}
|
|
35322
36034
|
done = true;
|
|
35323
36035
|
late = true;
|
|
35324
|
-
|
|
36036
|
+
if (afterTheWait === "keep") {
|
|
36037
|
+
keepLate(id, { state: "waiting", command, sentAt });
|
|
36038
|
+
}
|
|
35325
36039
|
lateTimer = setTimeout(() => {
|
|
35326
36040
|
finish({
|
|
35327
36041
|
ok: false,
|
|
@@ -35331,6 +36045,15 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
35331
36045
|
}, LATE_ANSWER_MS);
|
|
35332
36046
|
lateTimer.unref();
|
|
35333
36047
|
socket.unref();
|
|
36048
|
+
if (afterTheWait !== "keep") {
|
|
36049
|
+
settle({
|
|
36050
|
+
ok: false,
|
|
36051
|
+
reason: "busy",
|
|
36052
|
+
stillWithTheGame: true,
|
|
36053
|
+
message: `${still} Nothing was cancelled: the game runs it when it gets to it, and what it answers then is not kept, so ask again once the game answers. A game held at a breakpoint instead is one debug_state stack answers about.`
|
|
36054
|
+
});
|
|
36055
|
+
return;
|
|
36056
|
+
}
|
|
35334
36057
|
settle({
|
|
35335
36058
|
ok: false,
|
|
35336
36059
|
reason: "busy",
|
|
@@ -35338,71 +36061,112 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
|
|
|
35338
36061
|
message: `${still} Nothing was cancelled: a call that takes longer than the wait goes on and does everything it was asked, so read the state back rather than repeating it. runtime_invoke op result with requestId ${id} answers with its reply once it comes. A game held at a breakpoint instead is one debug_state stack answers about.`
|
|
35339
36062
|
});
|
|
35340
36063
|
}, timeoutMs);
|
|
35341
|
-
|
|
35342
|
-
|
|
35343
|
-
socket
|
|
36064
|
+
const open = () => {
|
|
36065
|
+
const opened = createConnection({ port: endpoint.port, host: dialAddress(endpoint.address) });
|
|
36066
|
+
socket = opened;
|
|
36067
|
+
opened.setEncoding("utf8");
|
|
36068
|
+
opened.on("connect", () => {
|
|
36069
|
+
opened.write(`${JSON.stringify({ id, command, params })}
|
|
35344
36070
|
`);
|
|
35345
|
-
|
|
35346
|
-
|
|
35347
|
-
|
|
35348
|
-
|
|
36071
|
+
});
|
|
36072
|
+
opened.on("data", (chunk) => {
|
|
36073
|
+
buffered += chunk;
|
|
36074
|
+
let newline = buffered.indexOf(`
|
|
35349
36075
|
`);
|
|
35350
|
-
|
|
35351
|
-
|
|
35352
|
-
|
|
35353
|
-
|
|
36076
|
+
while (newline !== -1 && (!done || late)) {
|
|
36077
|
+
const line = buffered.slice(0, newline).trim();
|
|
36078
|
+
buffered = buffered.slice(newline + 1);
|
|
36079
|
+
newline = buffered.indexOf(`
|
|
35354
36080
|
`);
|
|
35355
|
-
|
|
35356
|
-
|
|
35357
|
-
|
|
35358
|
-
|
|
35359
|
-
|
|
35360
|
-
|
|
35361
|
-
|
|
35362
|
-
|
|
35363
|
-
|
|
35364
|
-
|
|
35365
|
-
|
|
35366
|
-
|
|
35367
|
-
|
|
35368
|
-
|
|
35369
|
-
|
|
35370
|
-
|
|
35371
|
-
|
|
35372
|
-
|
|
35373
|
-
|
|
35374
|
-
|
|
36081
|
+
if (line === "") {
|
|
36082
|
+
continue;
|
|
36083
|
+
}
|
|
36084
|
+
let message;
|
|
36085
|
+
try {
|
|
36086
|
+
message = asParams(JSON.parse(line));
|
|
36087
|
+
} catch {
|
|
36088
|
+
continue;
|
|
36089
|
+
}
|
|
36090
|
+
if (!welcomed) {
|
|
36091
|
+
welcomed = true;
|
|
36092
|
+
taken();
|
|
36093
|
+
if (readString(message, "type") === "welcome") {
|
|
36094
|
+
const spoken = readNumber(message, "protocol");
|
|
36095
|
+
if (spoken !== RUNTIME_PROTOCOL) {
|
|
36096
|
+
finish({
|
|
36097
|
+
ok: false,
|
|
36098
|
+
reason: "protocol",
|
|
36099
|
+
message: `The game (${describe2(endpoint)}) speaks runtime protocol ${spoken ?? "unknown"} and this server speaks ${RUNTIME_PROTOCOL}. Reinstall the runtime addon into the project.`
|
|
36100
|
+
});
|
|
36101
|
+
return;
|
|
36102
|
+
}
|
|
36103
|
+
continue;
|
|
35375
36104
|
}
|
|
36105
|
+
}
|
|
36106
|
+
if (message["id"] !== id) {
|
|
35376
36107
|
continue;
|
|
35377
36108
|
}
|
|
36109
|
+
if (readString(message, "type") === "error") {
|
|
36110
|
+
finish({
|
|
36111
|
+
ok: false,
|
|
36112
|
+
reason: "error",
|
|
36113
|
+
message: readString(message, "message") ?? "unknown error"
|
|
36114
|
+
});
|
|
36115
|
+
return;
|
|
36116
|
+
}
|
|
36117
|
+
finish({ ok: true, payload: message, endpoint });
|
|
35378
36118
|
}
|
|
35379
|
-
|
|
35380
|
-
|
|
36119
|
+
});
|
|
36120
|
+
opened.on("error", (error) => {
|
|
36121
|
+
if (socket !== opened) {
|
|
36122
|
+
return;
|
|
35381
36123
|
}
|
|
35382
|
-
if (
|
|
35383
|
-
|
|
36124
|
+
if (error.code === "ECONNREFUSED" && !done) {
|
|
36125
|
+
if (alive(endpoint.pid)) {
|
|
36126
|
+
refusals += 1;
|
|
36127
|
+
socket = null;
|
|
36128
|
+
opened.destroy();
|
|
36129
|
+
redial = setTimeout(() => {
|
|
36130
|
+
redial = null;
|
|
36131
|
+
open();
|
|
36132
|
+
}, REDIAL_MS);
|
|
36133
|
+
return;
|
|
36134
|
+
}
|
|
36135
|
+
finish({
|
|
36136
|
+
ok: false,
|
|
36137
|
+
reason: "refused",
|
|
36138
|
+
message: `The game (${describe2(endpoint)}) has ended: its port refused the connection and its process is gone.`
|
|
36139
|
+
});
|
|
35384
36140
|
return;
|
|
35385
36141
|
}
|
|
35386
|
-
finish({
|
|
35387
|
-
|
|
35388
|
-
|
|
35389
|
-
|
|
35390
|
-
|
|
35391
|
-
ok: false,
|
|
35392
|
-
reason: "refused",
|
|
35393
|
-
message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: ${error.code ?? error.message}. It may still be starting, or be shutting down.`
|
|
36142
|
+
finish({
|
|
36143
|
+
ok: false,
|
|
36144
|
+
reason: "refused",
|
|
36145
|
+
message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: ${error.code ?? error.message}. It may still be starting or be shutting down.`
|
|
36146
|
+
});
|
|
35394
36147
|
});
|
|
35395
|
-
|
|
35396
|
-
|
|
35397
|
-
|
|
35398
|
-
|
|
35399
|
-
|
|
35400
|
-
|
|
36148
|
+
opened.on("close", () => {
|
|
36149
|
+
if (socket !== opened) {
|
|
36150
|
+
return;
|
|
36151
|
+
}
|
|
36152
|
+
finish({
|
|
36153
|
+
ok: false,
|
|
36154
|
+
reason: "refused",
|
|
36155
|
+
message: `The game (${describe2(endpoint)}) closed the connection before answering '${command}'.`
|
|
36156
|
+
});
|
|
35401
36157
|
});
|
|
36158
|
+
};
|
|
36159
|
+
waitForAPlace(placeOf(endpoint), (giveBack) => {
|
|
36160
|
+
if (done) {
|
|
36161
|
+
giveBack();
|
|
36162
|
+
return;
|
|
36163
|
+
}
|
|
36164
|
+
giveBackThePlace = giveBack;
|
|
36165
|
+
open();
|
|
35402
36166
|
});
|
|
35403
36167
|
});
|
|
35404
36168
|
}
|
|
35405
|
-
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, CONSOLE_BOUNDARY = "[gdharness] runtime listening on ", ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, judging = null, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers;
|
|
36169
|
+
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, CONSOLE_BOUNDARY = "[gdharness] runtime listening on ", ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, judging = null, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers, UNTAKEN_PER_GAME = 4, REDIAL_MS = 50, untaken;
|
|
35406
36170
|
var init_runtime_client = __esm(() => {
|
|
35407
36171
|
init_alive();
|
|
35408
36172
|
init_launch();
|
|
@@ -35415,17 +36179,18 @@ var init_runtime_client = __esm(() => {
|
|
|
35415
36179
|
verdicts = new Map;
|
|
35416
36180
|
LATE_ANSWER_MS = 30 * 60000;
|
|
35417
36181
|
lateAnswers = new Map;
|
|
36182
|
+
untaken = new Map;
|
|
35418
36183
|
});
|
|
35419
36184
|
|
|
35420
36185
|
// src/boot-note.ts
|
|
35421
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
35422
|
-
import { dirname as dirname11, join as
|
|
36186
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
|
|
36187
|
+
import { dirname as dirname11, join as join19 } from "node:path";
|
|
35423
36188
|
function bootNotePath(projectPath) {
|
|
35424
|
-
return
|
|
36189
|
+
return join19(projectPath, ".godot", "gdharness-boot.json");
|
|
35425
36190
|
}
|
|
35426
36191
|
function readScenes(projectPath) {
|
|
35427
36192
|
try {
|
|
35428
|
-
return asParams(asParams(JSON.parse(
|
|
36193
|
+
return asParams(asParams(JSON.parse(readFileSync15(bootNotePath(projectPath), "utf8")))["scenes"]);
|
|
35429
36194
|
} catch {
|
|
35430
36195
|
return {};
|
|
35431
36196
|
}
|
|
@@ -35467,10 +36232,10 @@ var init_boot_note = __esm(() => {
|
|
|
35467
36232
|
});
|
|
35468
36233
|
|
|
35469
36234
|
// src/breakpoint-note.ts
|
|
35470
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
35471
|
-
import { dirname as dirname12, isAbsolute as isAbsolute4, join as
|
|
36235
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
36236
|
+
import { dirname as dirname12, isAbsolute as isAbsolute4, join as join20, relative as relative3 } from "node:path";
|
|
35472
36237
|
function breakpointNotePath(projectPath) {
|
|
35473
|
-
return
|
|
36238
|
+
return join20(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
35474
36239
|
}
|
|
35475
36240
|
function writeBreakpointNote(projectPath, held) {
|
|
35476
36241
|
const path = breakpointNotePath(projectPath);
|
|
@@ -35496,7 +36261,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
35496
36261
|
}
|
|
35497
36262
|
function readBreakpointNote(projectPath) {
|
|
35498
36263
|
try {
|
|
35499
|
-
const parsed = JSON.parse(
|
|
36264
|
+
const parsed = JSON.parse(readFileSync16(breakpointNotePath(projectPath), "utf8"));
|
|
35500
36265
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
|
|
35501
36266
|
return [];
|
|
35502
36267
|
}
|
|
@@ -35511,7 +36276,7 @@ function readBreakpointNote(projectPath) {
|
|
|
35511
36276
|
}
|
|
35512
36277
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
35513
36278
|
if (lines.length > 0) {
|
|
35514
|
-
held.push({ scriptPath: realPathOr(
|
|
36279
|
+
held.push({ scriptPath: realPathOr(join20(projectPath, script)), lines });
|
|
35515
36280
|
}
|
|
35516
36281
|
}
|
|
35517
36282
|
return held;
|
|
@@ -35560,14 +36325,14 @@ var init_whole_file = __esm(() => {
|
|
|
35560
36325
|
});
|
|
35561
36326
|
|
|
35562
36327
|
// src/class-note.ts
|
|
35563
|
-
import { mkdirSync as mkdirSync8, readFileSync as
|
|
35564
|
-
import { dirname as dirname13, join as
|
|
36328
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync17 } from "node:fs";
|
|
36329
|
+
import { dirname as dirname13, join as join21 } from "node:path";
|
|
35565
36330
|
function classNotePath(projectPath) {
|
|
35566
|
-
return
|
|
36331
|
+
return join21(projectPath, ".godot", "gdharness-classes.json");
|
|
35567
36332
|
}
|
|
35568
36333
|
function readClassNote(projectPath) {
|
|
35569
36334
|
try {
|
|
35570
|
-
const fields = asParams(JSON.parse(
|
|
36335
|
+
const fields = asParams(JSON.parse(readFileSync17(classNotePath(projectPath), "utf8")));
|
|
35571
36336
|
const rebuiltWith = readArray(fields, "rebuiltWith");
|
|
35572
36337
|
if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
|
|
35573
36338
|
return null;
|
|
@@ -35609,16 +36374,16 @@ var init_class_note = __esm(() => {
|
|
|
35609
36374
|
});
|
|
35610
36375
|
|
|
35611
36376
|
// src/config-pin.ts
|
|
35612
|
-
import { existsSync as
|
|
35613
|
-
import { join as
|
|
36377
|
+
import { existsSync as existsSync17, readFileSync as readFileSync18 } from "node:fs";
|
|
36378
|
+
import { join as join22 } from "node:path";
|
|
35614
36379
|
function versionsNamedInConfig(projectPath) {
|
|
35615
|
-
const config =
|
|
35616
|
-
if (!
|
|
36380
|
+
const config = join22(projectPath, ".mcp.json");
|
|
36381
|
+
if (!existsSync17(config)) {
|
|
35617
36382
|
return [];
|
|
35618
36383
|
}
|
|
35619
36384
|
let text;
|
|
35620
36385
|
try {
|
|
35621
|
-
text =
|
|
36386
|
+
text = readFileSync18(config, "utf8");
|
|
35622
36387
|
JSON.parse(text);
|
|
35623
36388
|
} catch {
|
|
35624
36389
|
return [];
|
|
@@ -35639,10 +36404,10 @@ var init_config_pin = __esm(() => {
|
|
|
35639
36404
|
});
|
|
35640
36405
|
|
|
35641
36406
|
// src/cut-test-run.ts
|
|
35642
|
-
import { existsSync as
|
|
35643
|
-
import { dirname as dirname14, join as
|
|
36407
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync19, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
|
|
36408
|
+
import { dirname as dirname14, join as join23 } from "node:path";
|
|
35644
36409
|
function keptAt(projectPath) {
|
|
35645
|
-
return
|
|
36410
|
+
return join23(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
35646
36411
|
}
|
|
35647
36412
|
function keepCutTestRun(projectPath, run) {
|
|
35648
36413
|
const path = keptAt(projectPath);
|
|
@@ -35654,11 +36419,11 @@ function keepCutTestRun(projectPath, run) {
|
|
|
35654
36419
|
}
|
|
35655
36420
|
function takeCutTestRun(projectPath) {
|
|
35656
36421
|
const path = keptAt(projectPath);
|
|
35657
|
-
if (!
|
|
36422
|
+
if (!existsSync18(path)) {
|
|
35658
36423
|
return null;
|
|
35659
36424
|
}
|
|
35660
36425
|
try {
|
|
35661
|
-
const run = JSON.parse(
|
|
36426
|
+
const run = JSON.parse(readFileSync19(path, "utf8"));
|
|
35662
36427
|
rmSync9(path, { force: true });
|
|
35663
36428
|
return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
|
|
35664
36429
|
} catch {
|
|
@@ -36471,27 +37236,27 @@ var init_dap_client = __esm(() => {
|
|
|
36471
37236
|
import { createHash as createHash4 } from "node:crypto";
|
|
36472
37237
|
import {
|
|
36473
37238
|
closeSync,
|
|
36474
|
-
existsSync as
|
|
37239
|
+
existsSync as existsSync19,
|
|
36475
37240
|
mkdtempSync as mkdtempSync2,
|
|
36476
37241
|
openSync,
|
|
36477
|
-
readFileSync as
|
|
37242
|
+
readFileSync as readFileSync20,
|
|
36478
37243
|
rmSync as rmSync10,
|
|
36479
37244
|
statSync as statSync9,
|
|
36480
37245
|
writeFileSync as writeFileSync12,
|
|
36481
37246
|
writeSync
|
|
36482
37247
|
} from "node:fs";
|
|
36483
37248
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
36484
|
-
import { join as
|
|
37249
|
+
import { join as join24 } from "node:path";
|
|
36485
37250
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
36486
37251
|
function helperAssembly(directory = tmpdir5()) {
|
|
36487
|
-
return
|
|
37252
|
+
return join24(directory, `gdharness-desktop-${DIGEST}.dll`);
|
|
36488
37253
|
}
|
|
36489
37254
|
async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COMPILE_MS, wait = true) {
|
|
36490
37255
|
const assembly = helperAssembly(directory);
|
|
36491
37256
|
const lock = `${assembly}.compiling`;
|
|
36492
37257
|
const until = Date.now() + budgetMs;
|
|
36493
37258
|
while (Date.now() < until) {
|
|
36494
|
-
if (
|
|
37259
|
+
if (existsSync19(assembly)) {
|
|
36495
37260
|
return null;
|
|
36496
37261
|
}
|
|
36497
37262
|
let mine = false;
|
|
@@ -36509,7 +37274,7 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
|
|
|
36509
37274
|
}
|
|
36510
37275
|
}
|
|
36511
37276
|
try {
|
|
36512
|
-
const holder = Number(
|
|
37277
|
+
const holder = Number(readFileSync20(lock, "utf8").trim());
|
|
36513
37278
|
const old = Date.now() - statSync9(lock).mtimeMs > budgetMs;
|
|
36514
37279
|
if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
|
|
36515
37280
|
rmSync10(lock, { force: true });
|
|
@@ -36524,8 +37289,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
|
|
|
36524
37289
|
return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
|
|
36525
37290
|
}
|
|
36526
37291
|
async function compileHelper(directory, assembly, budgetMs) {
|
|
36527
|
-
const scratch = mkdtempSync2(
|
|
36528
|
-
const source =
|
|
37292
|
+
const scratch = mkdtempSync2(join24(directory, "gdharness-desktop-source-"));
|
|
37293
|
+
const source = join24(scratch, "helper.cs");
|
|
36529
37294
|
writeFileSync12(source, SOURCE, "utf8");
|
|
36530
37295
|
const literal = (path) => `'${path.replaceAll("'", "''")}'`;
|
|
36531
37296
|
const script = [
|
|
@@ -36563,7 +37328,7 @@ async function compileHelper(directory, assembly, budgetMs) {
|
|
|
36563
37328
|
});
|
|
36564
37329
|
compiler.once("close", (code) => {
|
|
36565
37330
|
clearTimeout(timer);
|
|
36566
|
-
resolve(
|
|
37331
|
+
resolve(existsSync19(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
|
|
36567
37332
|
});
|
|
36568
37333
|
});
|
|
36569
37334
|
} finally {
|
|
@@ -36730,25 +37495,25 @@ var init_desktop = __esm(() => {
|
|
|
36730
37495
|
// src/editor-log.ts
|
|
36731
37496
|
import {
|
|
36732
37497
|
closeSync as closeSync2,
|
|
36733
|
-
existsSync as
|
|
37498
|
+
existsSync as existsSync20,
|
|
36734
37499
|
mkdirSync as mkdirSync10,
|
|
36735
37500
|
openSync as openSync2,
|
|
36736
|
-
readFileSync as
|
|
37501
|
+
readFileSync as readFileSync21,
|
|
36737
37502
|
readSync,
|
|
36738
37503
|
rmSync as rmSync11,
|
|
36739
37504
|
statSync as statSync10,
|
|
36740
37505
|
writeFileSync as writeFileSync13
|
|
36741
37506
|
} from "node:fs";
|
|
36742
|
-
import { dirname as dirname15, join as
|
|
37507
|
+
import { dirname as dirname15, join as join25 } from "node:path";
|
|
36743
37508
|
function editorLogPath(projectPath) {
|
|
36744
|
-
return
|
|
37509
|
+
return join25(projectPath, ".godot", "gdharness-editor.log");
|
|
36745
37510
|
}
|
|
36746
37511
|
function editorLogNotePath(projectPath) {
|
|
36747
|
-
return
|
|
37512
|
+
return join25(projectPath, ".godot", "gdharness-editor.json");
|
|
36748
37513
|
}
|
|
36749
37514
|
function readEditorLogNote(projectPath) {
|
|
36750
37515
|
try {
|
|
36751
|
-
const fields = asParams(JSON.parse(
|
|
37516
|
+
const fields = asParams(JSON.parse(readFileSync21(editorLogNotePath(projectPath), "utf8")));
|
|
36752
37517
|
const pid = readNumber(fields, "pid");
|
|
36753
37518
|
const startedAt = readString(fields, "startedAt");
|
|
36754
37519
|
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
@@ -36789,7 +37554,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
36789
37554
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
36790
37555
|
}
|
|
36791
37556
|
const path = editorLogPath(projectPath);
|
|
36792
|
-
if (!
|
|
37557
|
+
if (!existsSync20(path)) {
|
|
36793
37558
|
return { kind: "not written" };
|
|
36794
37559
|
}
|
|
36795
37560
|
const log = new GameLog;
|
|
@@ -36847,7 +37612,7 @@ function foldProgress(text) {
|
|
|
36847
37612
|
function theFirstOf(path) {
|
|
36848
37613
|
const whole = statSync10(path).size;
|
|
36849
37614
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
36850
|
-
return { text:
|
|
37615
|
+
return { text: readFileSync21(path, "utf8") };
|
|
36851
37616
|
}
|
|
36852
37617
|
const handle = openSync2(path, "r");
|
|
36853
37618
|
try {
|
|
@@ -37008,7 +37773,7 @@ var init_editor_log = __esm(() => {
|
|
|
37008
37773
|
});
|
|
37009
37774
|
|
|
37010
37775
|
// src/rename.ts
|
|
37011
|
-
import { existsSync as
|
|
37776
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync22, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
|
|
37012
37777
|
import { dirname as dirname16, posix as posix2 } from "node:path";
|
|
37013
37778
|
|
|
37014
37779
|
class Script {
|
|
@@ -37713,11 +38478,11 @@ function rewritesOf(plan, texts, move) {
|
|
|
37713
38478
|
}
|
|
37714
38479
|
function applyRewrites(rewrites) {
|
|
37715
38480
|
for (const rewrite of rewrites) {
|
|
37716
|
-
const now =
|
|
38481
|
+
const now = readFileSync22(rewrite.absolute, "utf8");
|
|
37717
38482
|
if (now !== rewrite.before) {
|
|
37718
38483
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
37719
38484
|
}
|
|
37720
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
38485
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync21(rewrite.writeTo)) {
|
|
37721
38486
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
37722
38487
|
}
|
|
37723
38488
|
}
|
|
@@ -37731,7 +38496,7 @@ function applyRewrites(rewrites) {
|
|
|
37731
38496
|
let movedUid = false;
|
|
37732
38497
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
37733
38498
|
unlinkSync3(rewrite.absolute);
|
|
37734
|
-
if (
|
|
38499
|
+
if (existsSync21(`${rewrite.absolute}.uid`)) {
|
|
37735
38500
|
renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
37736
38501
|
movedUid = true;
|
|
37737
38502
|
}
|
|
@@ -37777,10 +38542,10 @@ var init_rename = __esm(() => {
|
|
|
37777
38542
|
});
|
|
37778
38543
|
|
|
37779
38544
|
// src/lsp_client.ts
|
|
37780
|
-
import { existsSync as
|
|
38545
|
+
import { existsSync as existsSync22, realpathSync as realpathSync4 } from "node:fs";
|
|
37781
38546
|
import { readFile, realpath } from "node:fs/promises";
|
|
37782
38547
|
import { createConnection as createConnection3 } from "node:net";
|
|
37783
|
-
import { dirname as dirname17, join as
|
|
38548
|
+
import { dirname as dirname17, join as join26, resolve as resolve5 } from "node:path";
|
|
37784
38549
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
37785
38550
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
37786
38551
|
function initializeTimeoutMs() {
|
|
@@ -38458,7 +39223,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
38458
39223
|
} catch {
|
|
38459
39224
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
38460
39225
|
}
|
|
38461
|
-
if (!
|
|
39226
|
+
if (!existsSync22(join26(projectPath, "project.godot"))) {
|
|
38462
39227
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
38463
39228
|
}
|
|
38464
39229
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -38618,8 +39383,8 @@ var init_lsp_client = __esm(() => {
|
|
|
38618
39383
|
});
|
|
38619
39384
|
|
|
38620
39385
|
// src/rename-symbol.ts
|
|
38621
|
-
import { existsSync as
|
|
38622
|
-
import { basename as basename7, dirname as dirname18, join as
|
|
39386
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync11, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
|
|
39387
|
+
import { basename as basename7, dirname as dirname18, join as join27, relative as relative4, resolve as resolve6 } from "node:path";
|
|
38623
39388
|
function resourcePathOf(projectPath, absolute) {
|
|
38624
39389
|
const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
38625
39390
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
@@ -38632,7 +39397,7 @@ function onDiskSpelling(path) {
|
|
|
38632
39397
|
let at = resolve6(path);
|
|
38633
39398
|
for (;; ) {
|
|
38634
39399
|
try {
|
|
38635
|
-
return
|
|
39400
|
+
return join27(realpathSync5.native(at), ...missing.reverse());
|
|
38636
39401
|
} catch {
|
|
38637
39402
|
const parent = dirname18(at);
|
|
38638
39403
|
if (parent === at) {
|
|
@@ -38685,7 +39450,7 @@ async function renameSymbol(request, services) {
|
|
|
38685
39450
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
38686
39451
|
};
|
|
38687
39452
|
}
|
|
38688
|
-
if (
|
|
39453
|
+
if (existsSync23(request.newScriptPath)) {
|
|
38689
39454
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
38690
39455
|
}
|
|
38691
39456
|
}
|
|
@@ -38861,7 +39626,7 @@ async function renameSymbol(request, services) {
|
|
|
38861
39626
|
scripts,
|
|
38862
39627
|
moves: new Map([[declaringPath, moveTo]]),
|
|
38863
39628
|
named: [declaringPath],
|
|
38864
|
-
exists: (path) =>
|
|
39629
|
+
exists: (path) => existsSync23(`${projectPath}/${path.slice("res://".length)}`)
|
|
38865
39630
|
}, plan);
|
|
38866
39631
|
}
|
|
38867
39632
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
|
|
@@ -39060,15 +39825,15 @@ ${mention.kind}`;
|
|
|
39060
39825
|
};
|
|
39061
39826
|
}
|
|
39062
39827
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
39063
|
-
const directory =
|
|
39828
|
+
const directory = join27(projectPath, ".godot", "gdharness-renames");
|
|
39064
39829
|
mkdirSync12(directory, { recursive: true });
|
|
39065
39830
|
detailWrites += 1;
|
|
39066
|
-
const path =
|
|
39831
|
+
const path = join27(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
39067
39832
|
writeFileSync15(path, `${JSON.stringify(detail, null, 2)}
|
|
39068
39833
|
`, "utf8");
|
|
39069
39834
|
const kept = readdirSync11(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
39070
39835
|
for (const old of kept.slice(10)) {
|
|
39071
|
-
rmSync12(
|
|
39836
|
+
rmSync12(join27(directory, old), { force: true });
|
|
39072
39837
|
}
|
|
39073
39838
|
return path;
|
|
39074
39839
|
}
|
|
@@ -39109,18 +39874,18 @@ var init_rename_symbol = __esm(() => {
|
|
|
39109
39874
|
|
|
39110
39875
|
// src/file-move.ts
|
|
39111
39876
|
import {
|
|
39112
|
-
existsSync as
|
|
39877
|
+
existsSync as existsSync24,
|
|
39113
39878
|
mkdirSync as mkdirSync13,
|
|
39114
39879
|
readdirSync as readdirSync12,
|
|
39115
|
-
readFileSync as
|
|
39880
|
+
readFileSync as readFileSync23,
|
|
39116
39881
|
renameSync as renameSync4,
|
|
39117
39882
|
statSync as statSync11,
|
|
39118
39883
|
writeFileSync as writeFileSync16
|
|
39119
39884
|
} from "node:fs";
|
|
39120
|
-
import { basename as basename8, dirname as dirname19, extname as extname3, join as
|
|
39885
|
+
import { basename as basename8, dirname as dirname19, extname as extname3, join as join28, relative as relative5 } from "node:path";
|
|
39121
39886
|
function filesUnder(directory) {
|
|
39122
39887
|
return readdirSync12(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
39123
|
-
const path =
|
|
39888
|
+
const path = join28(directory, entry.name);
|
|
39124
39889
|
return entry.isDirectory() ? filesUnder(path) : [path];
|
|
39125
39890
|
});
|
|
39126
39891
|
}
|
|
@@ -39141,8 +39906,8 @@ function unseenByTheEngine(projectPath, path) {
|
|
|
39141
39906
|
return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
|
|
39142
39907
|
}
|
|
39143
39908
|
if (at < steps.length - 1) {
|
|
39144
|
-
directory =
|
|
39145
|
-
if (
|
|
39909
|
+
directory = join28(directory, step);
|
|
39910
|
+
if (existsSync24(join28(directory, ".gdignore"))) {
|
|
39146
39911
|
return `${path} is under a directory holding a .gdignore, which the engine does not load`;
|
|
39147
39912
|
}
|
|
39148
39913
|
}
|
|
@@ -39154,12 +39919,12 @@ function installedAddonOf(projectPath, path) {
|
|
|
39154
39919
|
if (steps[0] !== "addons" || steps[1] === undefined) {
|
|
39155
39920
|
return null;
|
|
39156
39921
|
}
|
|
39157
|
-
return
|
|
39922
|
+
return existsSync24(join28(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
|
|
39158
39923
|
}
|
|
39159
39924
|
function binaryResources(projectPath) {
|
|
39160
39925
|
const found = [];
|
|
39161
39926
|
const visit = (directory, prefix) => {
|
|
39162
|
-
if (
|
|
39927
|
+
if (existsSync24(join28(directory, ".gdignore"))) {
|
|
39163
39928
|
return;
|
|
39164
39929
|
}
|
|
39165
39930
|
for (const entry of readdirSync12(directory, { withFileTypes: true })) {
|
|
@@ -39168,7 +39933,7 @@ function binaryResources(projectPath) {
|
|
|
39168
39933
|
}
|
|
39169
39934
|
const path = `${prefix}${entry.name}`;
|
|
39170
39935
|
if (entry.isDirectory()) {
|
|
39171
|
-
visit(
|
|
39936
|
+
visit(join28(directory, entry.name), `${path}/`);
|
|
39172
39937
|
} else if (/\.(scn|res)$/i.test(entry.name)) {
|
|
39173
39938
|
found.push(path);
|
|
39174
39939
|
}
|
|
@@ -39178,7 +39943,7 @@ function binaryResources(projectPath) {
|
|
|
39178
39943
|
return found;
|
|
39179
39944
|
}
|
|
39180
39945
|
function targetPathOf(projectPath, absolute) {
|
|
39181
|
-
const inside = relative5(onDiskSpelling(projectPath),
|
|
39946
|
+
const inside = relative5(onDiskSpelling(projectPath), join28(onDiskSpelling(dirname19(absolute)), basename8(absolute)));
|
|
39182
39947
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
39183
39948
|
return null;
|
|
39184
39949
|
}
|
|
@@ -39196,7 +39961,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39196
39961
|
if (to === null) {
|
|
39197
39962
|
return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
|
|
39198
39963
|
}
|
|
39199
|
-
if (!
|
|
39964
|
+
if (!existsSync24(absoluteFrom)) {
|
|
39200
39965
|
return { ok: false, reason: `There is nothing at ${from} to move.` };
|
|
39201
39966
|
}
|
|
39202
39967
|
if (from === to) {
|
|
@@ -39225,7 +39990,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39225
39990
|
if (isDirectory && to.startsWith(`${from}/`)) {
|
|
39226
39991
|
return { ok: false, reason: `${from} cannot be moved into itself.` };
|
|
39227
39992
|
}
|
|
39228
|
-
if (
|
|
39993
|
+
if (existsSync24(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
|
|
39229
39994
|
return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
|
|
39230
39995
|
}
|
|
39231
39996
|
const unseen = unseenByTheEngine(projectPath, to);
|
|
@@ -39233,7 +39998,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39233
39998
|
return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
|
|
39234
39999
|
}
|
|
39235
40000
|
if (!isDirectory) {
|
|
39236
|
-
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null &&
|
|
40001
|
+
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync24(join28(projectPath, path.slice("res://".length))));
|
|
39237
40002
|
if (owner !== undefined && owner !== null) {
|
|
39238
40003
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
39239
40004
|
}
|
|
@@ -39255,7 +40020,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39255
40020
|
} else {
|
|
39256
40021
|
moves.set(from, to);
|
|
39257
40022
|
for (const sidecar of SIDECARS) {
|
|
39258
|
-
if (
|
|
40023
|
+
if (existsSync24(`${absoluteFrom}${sidecar}`)) {
|
|
39259
40024
|
moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
|
|
39260
40025
|
}
|
|
39261
40026
|
}
|
|
@@ -39267,12 +40032,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39267
40032
|
scripts,
|
|
39268
40033
|
moves,
|
|
39269
40034
|
named: [from],
|
|
39270
|
-
exists: (path) =>
|
|
40035
|
+
exists: (path) => existsSync24(join28(projectPath, path.slice("res://".length)))
|
|
39271
40036
|
}, { edits: [], mentions: [] });
|
|
39272
40037
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
|
|
39273
40038
|
const binaries = [];
|
|
39274
40039
|
for (const path of binaryResources(projectPath)) {
|
|
39275
|
-
const bytes =
|
|
40040
|
+
const bytes = readFileSync23(join28(projectPath, path.slice("res://".length)));
|
|
39276
40041
|
const names = [...moves.keys()].find((moving) => bytes.includes(moving));
|
|
39277
40042
|
if (names !== undefined) {
|
|
39278
40043
|
binaries.push({
|
|
@@ -39291,7 +40056,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39291
40056
|
}));
|
|
39292
40057
|
const left = leftAloneReport(mentions);
|
|
39293
40058
|
const rewrites = rewritesOf(plan, texts, null);
|
|
39294
|
-
const absoluteOf = (path) =>
|
|
40059
|
+
const absoluteOf = (path) => join28(projectPath, path.slice("res://".length));
|
|
39295
40060
|
const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
|
|
39296
40061
|
const moved = [...moves].map(([source, target]) => {
|
|
39297
40062
|
const rewrite = rewritten.get(source);
|
|
@@ -39333,18 +40098,18 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
39333
40098
|
}
|
|
39334
40099
|
function applyFileMove(outcome) {
|
|
39335
40100
|
for (const rewrite of outcome.inPlace) {
|
|
39336
|
-
if (
|
|
40101
|
+
if (readFileSync23(rewrite.absolute, "utf8") !== rewrite.before) {
|
|
39337
40102
|
throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
|
|
39338
40103
|
}
|
|
39339
40104
|
}
|
|
39340
40105
|
for (const file of outcome.moved) {
|
|
39341
|
-
if (!
|
|
40106
|
+
if (!existsSync24(file.absoluteFrom)) {
|
|
39342
40107
|
throw new Error(`${file.from} is gone from disk; nothing was written`);
|
|
39343
40108
|
}
|
|
39344
|
-
if (file.text !== null &&
|
|
40109
|
+
if (file.text !== null && readFileSync23(file.absoluteFrom, "utf8") !== file.text.before) {
|
|
39345
40110
|
throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
|
|
39346
40111
|
}
|
|
39347
|
-
if (
|
|
40112
|
+
if (existsSync24(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
|
|
39348
40113
|
throw new Error(`${file.to} already exists; nothing was written`);
|
|
39349
40114
|
}
|
|
39350
40115
|
}
|
|
@@ -42773,701 +43538,202 @@ var init_godot_bridge = __esm(() => {
|
|
|
42773
43538
|
this.emitBridgeEvent("tool_start", {
|
|
42774
43539
|
tool: toolName,
|
|
42775
43540
|
id: requestId,
|
|
42776
|
-
args
|
|
42777
|
-
});
|
|
42778
|
-
try {
|
|
42779
|
-
this.sendMessage(message);
|
|
42780
|
-
} catch (error) {
|
|
42781
|
-
clearTimeout(timeout);
|
|
42782
|
-
this.pendingRequests.delete(requestId);
|
|
42783
|
-
reject(toError(error));
|
|
42784
|
-
}
|
|
42785
|
-
});
|
|
42786
|
-
}
|
|
42787
|
-
sendMessage(message) {
|
|
42788
|
-
if (this.socket?.readyState !== import_websocket.default.OPEN) {
|
|
42789
|
-
throw new Refusal("Godot is not connected");
|
|
42790
|
-
}
|
|
42791
|
-
this.socket.send(JSON.stringify(message));
|
|
42792
|
-
}
|
|
42793
|
-
startKeepalive() {
|
|
42794
|
-
this.stopKeepalive();
|
|
42795
|
-
this.pingInterval = setInterval(() => {
|
|
42796
|
-
if (!this.isConnected()) {
|
|
42797
|
-
return;
|
|
42798
|
-
}
|
|
42799
|
-
try {
|
|
42800
|
-
const ping = { type: "ping" };
|
|
42801
|
-
this.sendMessage(ping);
|
|
42802
|
-
} catch (error) {
|
|
42803
|
-
this.log("warn", `Failed to send ping: ${errorMessage(error)}`);
|
|
42804
|
-
}
|
|
42805
|
-
}, KEEPALIVE_INTERVAL_MS);
|
|
42806
|
-
}
|
|
42807
|
-
stopKeepalive() {
|
|
42808
|
-
if (!this.pingInterval) {
|
|
42809
|
-
return;
|
|
42810
|
-
}
|
|
42811
|
-
clearInterval(this.pingInterval);
|
|
42812
|
-
this.pingInterval = null;
|
|
42813
|
-
}
|
|
42814
|
-
belongsElsewhere(theirProject) {
|
|
42815
|
-
if (this.ownProject === null || theirProject.trim() === "") {
|
|
42816
|
-
return false;
|
|
42817
|
-
}
|
|
42818
|
-
return !isSameDirectory(this.ownProject, theirProject);
|
|
42819
|
-
}
|
|
42820
|
-
sendElsewhere(theirProject) {
|
|
42821
|
-
const said = `this server serves ${this.ownProject ?? ""}, and that editor has ${theirProject} open`;
|
|
42822
|
-
this.log("warn", `Turning away an editor from another project: ${said}`);
|
|
42823
|
-
const stranger = this.socket;
|
|
42824
|
-
this.socket = null;
|
|
42825
|
-
this.connectionInfo = null;
|
|
42826
|
-
this.stopKeepalive();
|
|
42827
|
-
const fits = closeReason(said, "start") === said;
|
|
42828
|
-
stranger?.close(OTHER_PROJECT_CLOSE_CODE, fits ? said : closeReason(`this server serves ${this.ownProject ?? ""}`, "end"));
|
|
42829
|
-
}
|
|
42830
|
-
elsewhereUnlessOneShot(theirProject, pid, openedByAServer) {
|
|
42831
|
-
const known = pid === undefined ? undefined : this.oneShots.get(pid);
|
|
42832
|
-
if (pid !== undefined && known !== undefined) {
|
|
42833
|
-
this.turnAway(pid, known);
|
|
42834
|
-
return;
|
|
42835
|
-
}
|
|
42836
|
-
const info = this.connectionInfo;
|
|
42837
|
-
const socket = this.socket;
|
|
42838
|
-
const reader = this.oneShotReader;
|
|
42839
|
-
if (pid === undefined || openedByAServer || info === null || reader === null) {
|
|
42840
|
-
this.sendElsewhere(theirProject);
|
|
42841
|
-
return;
|
|
42842
|
-
}
|
|
42843
|
-
info.identifying = true;
|
|
42844
|
-
reader(pid).catch(() => {
|
|
42845
|
-
return;
|
|
42846
|
-
}).then((option) => {
|
|
42847
|
-
if (this.socket !== socket || this.connectionInfo !== info) {
|
|
42848
|
-
return;
|
|
42849
|
-
}
|
|
42850
|
-
if (typeof option === "string") {
|
|
42851
|
-
this.oneShots.set(pid, option);
|
|
42852
|
-
this.turnAway(pid, option);
|
|
42853
|
-
return;
|
|
42854
|
-
}
|
|
42855
|
-
this.sendElsewhere(theirProject);
|
|
42856
|
-
});
|
|
42857
|
-
}
|
|
42858
|
-
identify(pid) {
|
|
42859
|
-
const known = this.oneShots.get(pid);
|
|
42860
|
-
if (known !== undefined) {
|
|
42861
|
-
this.turnAway(pid, known);
|
|
42862
|
-
return;
|
|
42863
|
-
}
|
|
42864
|
-
const info = this.connectionInfo;
|
|
42865
|
-
const socket = this.socket;
|
|
42866
|
-
const reader = this.oneShotReader;
|
|
42867
|
-
if (info === null || reader === null) {
|
|
42868
|
-
return;
|
|
42869
|
-
}
|
|
42870
|
-
info.identifying = true;
|
|
42871
|
-
reader(pid).catch(() => {
|
|
42872
|
-
return;
|
|
42873
|
-
}).then((option) => {
|
|
42874
|
-
if (this.socket !== socket || this.connectionInfo !== info) {
|
|
42875
|
-
return;
|
|
42876
|
-
}
|
|
42877
|
-
if (typeof option === "string") {
|
|
42878
|
-
this.oneShots.set(pid, option);
|
|
42879
|
-
this.turnAway(pid, option);
|
|
42880
|
-
return;
|
|
42881
|
-
}
|
|
42882
|
-
info.identifying = false;
|
|
42883
|
-
this.log("info", `Godot editor identified: pid ${pid}`);
|
|
42884
|
-
this.emitBridgeEvent("godot_connected", { projectPath: info.projectPath });
|
|
42885
|
-
});
|
|
42886
|
-
}
|
|
42887
|
-
turnAway(pid, option) {
|
|
42888
|
-
const said = `pid ${pid} was started with ${option}, which does one job and quits, so it is not an editor`;
|
|
42889
|
-
this.log("info", `Turning away an engine run: ${said}`);
|
|
42890
|
-
this.turnedAway = { pid, option, at: new Date };
|
|
42891
|
-
const run = this.socket;
|
|
42892
|
-
this.socket = null;
|
|
42893
|
-
this.connectionInfo = null;
|
|
42894
|
-
this.stopKeepalive();
|
|
42895
|
-
run?.close(ONE_SHOT_CLOSE_CODE, closeReason(said, "start"));
|
|
42896
|
-
}
|
|
42897
|
-
handleDisconnect(disconnectedSocket, reason) {
|
|
42898
|
-
if (disconnectedSocket && this.socket && disconnectedSocket !== this.socket) {
|
|
42899
|
-
this.log("debug", "Ignoring stale Godot socket disconnect event");
|
|
42900
|
-
return;
|
|
42901
|
-
}
|
|
42902
|
-
this.stopKeepalive();
|
|
42903
|
-
this.socket = null;
|
|
42904
|
-
this.connectionInfo = null;
|
|
42905
|
-
this.emitBridgeEvent("godot_disconnected", {});
|
|
42906
|
-
this.rejectAllPending(reason);
|
|
42907
|
-
this.resourceQueues.clear();
|
|
42908
|
-
}
|
|
42909
|
-
emitBridgeEvent(eventName, payload) {
|
|
42910
|
-
this.emit(eventName, payload);
|
|
42911
|
-
}
|
|
42912
|
-
rejectAllPending(error) {
|
|
42913
|
-
for (const pending of this.pendingRequests.values()) {
|
|
42914
|
-
clearTimeout(pending.timeout);
|
|
42915
|
-
pending.reject(error);
|
|
42916
|
-
}
|
|
42917
|
-
this.pendingRequests.clear();
|
|
42918
|
-
}
|
|
42919
|
-
enqueueResourceRequest(resourceKey, task) {
|
|
42920
|
-
const previous = this.resourceQueues.get(resourceKey) ?? Promise.resolve();
|
|
42921
|
-
const taskPromise = previous.catch(() => {
|
|
42922
|
-
return;
|
|
42923
|
-
}).then(task);
|
|
42924
|
-
const tail = taskPromise.then(() => {
|
|
42925
|
-
return;
|
|
42926
|
-
}, () => {
|
|
42927
|
-
return;
|
|
42928
|
-
});
|
|
42929
|
-
this.resourceQueues.set(resourceKey, tail);
|
|
42930
|
-
return taskPromise.finally(() => {
|
|
42931
|
-
if (this.resourceQueues.get(resourceKey) === tail) {
|
|
42932
|
-
this.resourceQueues.delete(resourceKey);
|
|
42933
|
-
}
|
|
42934
|
-
});
|
|
42935
|
-
}
|
|
42936
|
-
getResourceKey(args) {
|
|
42937
|
-
const scenePath = this.getStringArg(args, "scenePath") ?? this.getStringArg(args, "scene_path");
|
|
42938
|
-
if (scenePath) {
|
|
42939
|
-
return `scene:${scenePath}`;
|
|
42940
|
-
}
|
|
42941
|
-
const resourcePath = this.getStringArg(args, "resourcePath") ?? this.getStringArg(args, "resource_path");
|
|
42942
|
-
if (resourcePath) {
|
|
42943
|
-
return `resource:${resourcePath}`;
|
|
42944
|
-
}
|
|
42945
|
-
return;
|
|
42946
|
-
}
|
|
42947
|
-
getStringArg(args, key) {
|
|
42948
|
-
const value = args[key];
|
|
42949
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
42950
|
-
}
|
|
42951
|
-
isIncomingMessage(value) {
|
|
42952
|
-
if (!value || typeof value !== "object") {
|
|
42953
|
-
return false;
|
|
42954
|
-
}
|
|
42955
|
-
const message = value;
|
|
42956
|
-
const type = message["type"];
|
|
42957
|
-
if (type !== "tool_result" && type !== "pong" && type !== "godot_ready") {
|
|
42958
|
-
return false;
|
|
42959
|
-
}
|
|
42960
|
-
if (type === "pong") {
|
|
42961
|
-
return true;
|
|
42962
|
-
}
|
|
42963
|
-
if (type === "godot_ready") {
|
|
42964
|
-
return typeof message["project_path"] === "string";
|
|
42965
|
-
}
|
|
42966
|
-
return typeof message["id"] === "string" && typeof message["success"] === "boolean" && (message["error"] === undefined || typeof message["error"] === "string") && (message["busy"] === undefined || typeof message["busy"] === "boolean");
|
|
42967
|
-
}
|
|
42968
|
-
log(level, message) {
|
|
42969
|
-
console.error(`[${new Date().toISOString()}] [GodotBridge:${level.toUpperCase()}] ${message}`);
|
|
42970
|
-
}
|
|
42971
|
-
};
|
|
42972
|
-
});
|
|
42973
|
-
|
|
42974
|
-
// src/junit.ts
|
|
42975
|
-
function decoded(text) {
|
|
42976
|
-
return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/g, (whole, entity) => {
|
|
42977
|
-
if (entity.startsWith("#")) {
|
|
42978
|
-
const code = entity.startsWith("#x") ? Number.parseInt(entity.slice(2), 16) : Number.parseInt(entity.slice(1), 10);
|
|
42979
|
-
if (!Number.isInteger(code) || code < 0 || code > 1114111) {
|
|
42980
|
-
throw new MalformedReportError(`Not a character: ${whole}`);
|
|
42981
|
-
}
|
|
42982
|
-
return String.fromCodePoint(code);
|
|
42983
|
-
}
|
|
42984
|
-
const named = ENTITIES[entity];
|
|
42985
|
-
if (named === undefined) {
|
|
42986
|
-
throw new MalformedReportError(`Unknown entity ${whole}`);
|
|
42987
|
-
}
|
|
42988
|
-
return named;
|
|
42989
|
-
});
|
|
42990
|
-
}
|
|
42991
|
-
function tagEnd(source, from) {
|
|
42992
|
-
let quote = null;
|
|
42993
|
-
for (let at = from + 1;at < source.length; at += 1) {
|
|
42994
|
-
const character = source[at];
|
|
42995
|
-
if (quote !== null) {
|
|
42996
|
-
if (character === quote) {
|
|
42997
|
-
quote = null;
|
|
42998
|
-
}
|
|
42999
|
-
} else if (character === '"' || character === "'") {
|
|
43000
|
-
quote = character;
|
|
43001
|
-
} else if (character === ">") {
|
|
43002
|
-
return at;
|
|
43003
|
-
}
|
|
43004
|
-
}
|
|
43005
|
-
return -1;
|
|
43006
|
-
}
|
|
43007
|
-
function parseXml(source) {
|
|
43008
|
-
const root = { name: "", attributes: {}, children: [], text: "" };
|
|
43009
|
-
const open = [root];
|
|
43010
|
-
let at = 0;
|
|
43011
|
-
const current = () => {
|
|
43012
|
-
const element = open.at(-1);
|
|
43013
|
-
if (element === undefined) {
|
|
43014
|
-
throw new MalformedReportError("A close tag with nothing open");
|
|
43015
|
-
}
|
|
43016
|
-
return element;
|
|
43017
|
-
};
|
|
43018
|
-
while (at < source.length) {
|
|
43019
|
-
const next = source.indexOf("<", at);
|
|
43020
|
-
if (next === -1) {
|
|
43021
|
-
current().text += decoded(source.slice(at));
|
|
43022
|
-
break;
|
|
43023
|
-
}
|
|
43024
|
-
current().text += decoded(source.slice(at, next));
|
|
43025
|
-
at = next;
|
|
43026
|
-
if (source.startsWith("<![CDATA[", at)) {
|
|
43027
|
-
const end = source.indexOf("]]>", at);
|
|
43028
|
-
if (end === -1) {
|
|
43029
|
-
throw new MalformedReportError("CDATA that never ends");
|
|
43030
|
-
}
|
|
43031
|
-
current().text += source.slice(at + "<![CDATA[".length, end);
|
|
43032
|
-
at = end + "]]>".length;
|
|
43033
|
-
continue;
|
|
43034
|
-
}
|
|
43035
|
-
if (source.startsWith("<!--", at)) {
|
|
43036
|
-
const end = source.indexOf("-->", at);
|
|
43037
|
-
if (end === -1) {
|
|
43038
|
-
throw new MalformedReportError("A comment that never ends");
|
|
43039
|
-
}
|
|
43040
|
-
at = end + "-->".length;
|
|
43041
|
-
continue;
|
|
43042
|
-
}
|
|
43043
|
-
if (source.startsWith("<?", at) || source.startsWith("<!", at)) {
|
|
43044
|
-
const end = source.indexOf(">", at);
|
|
43045
|
-
if (end === -1) {
|
|
43046
|
-
throw new MalformedReportError("A declaration that never ends");
|
|
43047
|
-
}
|
|
43048
|
-
at = end + 1;
|
|
43049
|
-
continue;
|
|
43050
|
-
}
|
|
43051
|
-
const end = tagEnd(source, at);
|
|
43052
|
-
if (end === -1) {
|
|
43053
|
-
throw new MalformedReportError("A tag that never ends");
|
|
43054
|
-
}
|
|
43055
|
-
const tag = source.slice(at + 1, end);
|
|
43056
|
-
at = end + 1;
|
|
43057
|
-
if (tag.startsWith("/")) {
|
|
43058
|
-
const closing = tag.slice(1).trim();
|
|
43059
|
-
const element = current();
|
|
43060
|
-
if (element === root || element.name !== closing) {
|
|
43061
|
-
throw new MalformedReportError(`</${closing}> closes <${element.name || "nothing"}>`);
|
|
43062
|
-
}
|
|
43063
|
-
open.pop();
|
|
43064
|
-
continue;
|
|
43065
|
-
}
|
|
43066
|
-
const selfClosing = tag.endsWith("/");
|
|
43067
|
-
const body = selfClosing ? tag.slice(0, -1) : tag;
|
|
43068
|
-
const nameMatch = /^([^\s/>]+)/.exec(body);
|
|
43069
|
-
if (!nameMatch?.[1]) {
|
|
43070
|
-
throw new MalformedReportError(`A tag with no name: <${tag}>`);
|
|
43071
|
-
}
|
|
43072
|
-
const attributes = {};
|
|
43073
|
-
let read = nameMatch[0].length;
|
|
43074
|
-
ATTRIBUTE.lastIndex = read;
|
|
43075
|
-
let attribute = ATTRIBUTE.exec(body);
|
|
43076
|
-
while (attribute) {
|
|
43077
|
-
attributes[attribute[1] ?? ""] = decoded(attribute[2] ?? attribute[3] ?? "");
|
|
43078
|
-
read = ATTRIBUTE.lastIndex;
|
|
43079
|
-
attribute = ATTRIBUTE.exec(body);
|
|
43080
|
-
}
|
|
43081
|
-
if (body.slice(read).trim() !== "") {
|
|
43082
|
-
throw new MalformedReportError(`Unreadable attributes in <${tag}>`);
|
|
43083
|
-
}
|
|
43084
|
-
const element = { name: nameMatch[1], attributes, children: [], text: "" };
|
|
43085
|
-
current().children.push(element);
|
|
43086
|
-
if (!selfClosing) {
|
|
43087
|
-
open.push(element);
|
|
43088
|
-
}
|
|
43089
|
-
}
|
|
43090
|
-
if (open.length !== 1) {
|
|
43091
|
-
throw new MalformedReportError(`<${current().name}> is never closed`);
|
|
43092
|
-
}
|
|
43093
|
-
return root;
|
|
43094
|
-
}
|
|
43095
|
-
function count(attributes, name) {
|
|
43096
|
-
const value = Number(attributes[name] ?? "0");
|
|
43097
|
-
return Number.isFinite(value) ? value : 0;
|
|
43098
|
-
}
|
|
43099
|
-
function ignoredYetRan(report, ignored) {
|
|
43100
|
-
const found = [];
|
|
43101
|
-
for (const entry of ignored) {
|
|
43102
|
-
const bare = entry.replace("res://", "");
|
|
43103
|
-
const at = bare.lastIndexOf(":");
|
|
43104
|
-
const suiteNamed = at === -1 ? null : bare.slice(0, at);
|
|
43105
|
-
const testNamed = at === -1 ? bare : bare.slice(at + 1);
|
|
43106
|
-
for (const suite of report.suites) {
|
|
43107
|
-
const suiteMatches = suiteNamed === null || suite.name === suiteNamed || (suite.path ?? "").includes(suiteNamed);
|
|
43108
|
-
const ran = suiteMatches ? suite.cases.find((one) => one.name === testNamed) : undefined;
|
|
43109
|
-
if (ran !== undefined) {
|
|
43110
|
-
found.push({ entry, ran: `${suite.name}:${ran.name}` });
|
|
43111
|
-
}
|
|
43112
|
-
}
|
|
43113
|
-
}
|
|
43114
|
-
return found;
|
|
43115
|
-
}
|
|
43116
|
-
function caseOf(suite, element) {
|
|
43117
|
-
const of = (kind) => element.children.filter((child) => child.name === kind).map((child) => {
|
|
43118
|
-
const detail = child.text.trim();
|
|
43119
|
-
return { kind, message: child.attributes["message"] ?? null, detail: detail === "" ? null : detail };
|
|
43120
|
-
});
|
|
43121
|
-
const reports = [...of("failure"), ...of("error"), ...of("skipped")];
|
|
43122
|
-
const status = reports.some((report) => report.kind === "error") ? "error" : reports.some((report) => report.kind === "failure") ? "failed" : reports.length > 0 ? "skipped" : "passed";
|
|
43123
|
-
return {
|
|
43124
|
-
suite: suite.attributes["name"] ?? "",
|
|
43125
|
-
name: element.attributes["name"] ?? "",
|
|
43126
|
-
status,
|
|
43127
|
-
time: count(element.attributes, "time"),
|
|
43128
|
-
message: reports[0]?.message ?? null,
|
|
43129
|
-
detail: reports[0]?.detail ?? null,
|
|
43130
|
-
reports
|
|
43131
|
-
};
|
|
43132
|
-
}
|
|
43133
|
-
function failedAnAssertion(entry) {
|
|
43134
|
-
return entry.reports.some((report) => report.kind === "failure");
|
|
43135
|
-
}
|
|
43136
|
-
function pathOf(suite) {
|
|
43137
|
-
const directory = suite.attributes["package"];
|
|
43138
|
-
const name = suite.attributes["name"];
|
|
43139
|
-
return directory && name ? `res://${directory}/${name}.gd` : null;
|
|
43140
|
-
}
|
|
43141
|
-
function parseJUnit(xml) {
|
|
43142
|
-
const document = parseXml(xml);
|
|
43143
|
-
const top = document.children.find((child) => child.name === "testsuites" || child.name === "testsuite");
|
|
43144
|
-
if (top === undefined) {
|
|
43145
|
-
throw new MalformedReportError("No <testsuites> or <testsuite> in the report");
|
|
43146
|
-
}
|
|
43147
|
-
const suiteElements = top.name === "testsuite" ? [top] : top.children.filter((child) => child.name === "testsuite");
|
|
43148
|
-
const suites = suiteElements.map((suite) => {
|
|
43149
|
-
const cases = suite.children.filter((child) => child.name === "testcase").map((element) => caseOf(suite, element));
|
|
43150
|
-
const failures = cases.filter(failedAnAssertion).length;
|
|
43151
|
-
const errors = cases.filter((entry) => entry.status === "error").length;
|
|
43152
|
-
const written = (kind) => suite.children.filter((child) => child.name === "testcase").reduce((sum, element) => sum + element.children.filter((child) => child.name === kind).length, 0);
|
|
43153
|
-
return {
|
|
43154
|
-
name: suite.attributes["name"] ?? "",
|
|
43155
|
-
path: pathOf(suite),
|
|
43156
|
-
tests: cases.length,
|
|
43157
|
-
discovered: count(suite.attributes, "tests"),
|
|
43158
|
-
failures,
|
|
43159
|
-
errors,
|
|
43160
|
-
hookFailures: Math.max(0, count(suite.attributes, "failures") - written("failure")),
|
|
43161
|
-
hookErrors: Math.max(0, count(suite.attributes, "errors") - written("error")),
|
|
43162
|
-
skipped: cases.filter((entry) => entry.status === "skipped").length,
|
|
43163
|
-
time: count(suite.attributes, "time"),
|
|
43164
|
-
cases
|
|
43165
|
-
};
|
|
43166
|
-
});
|
|
43167
|
-
const all = suites.flatMap((suite) => suite.cases);
|
|
43168
|
-
return {
|
|
43169
|
-
tests: all.length,
|
|
43170
|
-
failures: all.filter(failedAnAssertion).length,
|
|
43171
|
-
errors: all.filter((entry) => entry.status === "error").length,
|
|
43172
|
-
hookFailures: suites.reduce((sum, suite) => sum + suite.hookFailures, 0),
|
|
43173
|
-
hookErrors: suites.reduce((sum, suite) => sum + suite.hookErrors, 0),
|
|
43174
|
-
skipped: all.filter((entry) => entry.status === "skipped").length,
|
|
43175
|
-
time: suites.reduce((sum, suite) => sum + suite.time, 0),
|
|
43176
|
-
suites
|
|
43177
|
-
};
|
|
43178
|
-
}
|
|
43179
|
-
function hookFailuresPrinted(printed) {
|
|
43180
|
-
return printed.flatMap((line) => {
|
|
43181
|
-
const found = HOOK_FAILURE.exec(line);
|
|
43182
|
-
return found === null ? [] : [{ hook: found[2] ?? "", path: found[3] ?? "", line: Number(found[4]), message: found[1] ?? "" }];
|
|
43183
|
-
});
|
|
43184
|
-
}
|
|
43185
|
-
function whyNoReport(printed, asked) {
|
|
43186
|
-
const missing = printed.map((line) => NO_SUCH_PATH.exec(line)).find((found) => found !== null);
|
|
43187
|
-
if (missing !== undefined) {
|
|
43188
|
-
return `nothing at ${missing[1] ?? asked}`;
|
|
43189
|
-
}
|
|
43190
|
-
return printed.some((line) => NOTHING_RAN.test(line)) ? `no test cases found at ${asked}` : null;
|
|
43191
|
-
}
|
|
43192
|
-
function scriptErrorsPrinted(printed) {
|
|
43193
|
-
return discoveryListing(printed)?.errors ?? [];
|
|
43194
|
-
}
|
|
43195
|
-
function discoveryListing(printed) {
|
|
43196
|
-
const start = printed.findIndex((line) => line.includes(DISCOVERY_FAILED));
|
|
43197
|
-
if (start < 0) {
|
|
43198
|
-
return null;
|
|
43199
|
-
}
|
|
43200
|
-
const found = [];
|
|
43201
|
-
let message = null;
|
|
43202
|
-
let to = start + 1;
|
|
43203
|
-
for (const line of printed.slice(start + 1)) {
|
|
43204
|
-
const at = /^\s*at (res:\/\/.+):(\d+)\s*$/.exec(line);
|
|
43205
|
-
if (at !== null && message !== null) {
|
|
43206
|
-
found.push({ path: at[1] ?? "", line: Number(at[2]) === 0 ? null : Number(at[2]), message });
|
|
43207
|
-
message = null;
|
|
43208
|
-
} else if (at === null && message === null && /^ {2}\S/.test(line)) {
|
|
43209
|
-
message = line.trim();
|
|
43210
|
-
} else {
|
|
43211
|
-
break;
|
|
43212
|
-
}
|
|
43213
|
-
to += 1;
|
|
43214
|
-
}
|
|
43215
|
-
const errors = found.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : (a.line ?? 0) - (b.line ?? 0));
|
|
43216
|
-
return { errors, from: start, to };
|
|
43217
|
-
}
|
|
43218
|
-
function unspelled(text) {
|
|
43219
|
-
return text.replace(/<(?:LF|CR|TAB|BS|FF|VT|BEL|ESC|DEL|0x[0-9A-F]{2})>/g, (spelling) => spelling.startsWith("<0x") ? String.fromCharCode(Number.parseInt(spelling.slice(3, 5), 16)) : SPELLED[spelling] ?? spelling);
|
|
43220
|
-
}
|
|
43221
|
-
function readDiff(printed) {
|
|
43222
|
-
let merged = "";
|
|
43223
|
-
let actual = "";
|
|
43224
|
-
let background = null;
|
|
43225
|
-
let last = 0;
|
|
43226
|
-
const take = (text) => {
|
|
43227
|
-
merged += text;
|
|
43228
|
-
if (background === null) {
|
|
43229
|
-
actual += text;
|
|
43230
|
-
} else if (background === "added") {
|
|
43231
|
-
actual += unspelled(text);
|
|
43541
|
+
args
|
|
43542
|
+
});
|
|
43543
|
+
try {
|
|
43544
|
+
this.sendMessage(message);
|
|
43545
|
+
} catch (error) {
|
|
43546
|
+
clearTimeout(timeout);
|
|
43547
|
+
this.pendingRequests.delete(requestId);
|
|
43548
|
+
reject(toError(error));
|
|
43549
|
+
}
|
|
43550
|
+
});
|
|
43232
43551
|
}
|
|
43233
|
-
|
|
43234
|
-
|
|
43235
|
-
|
|
43236
|
-
|
|
43237
|
-
|
|
43238
|
-
if (codes[0] === "48" && codes[1] === "2") {
|
|
43239
|
-
background = Number(codes[2]) > Number(codes[3]) ? "missing" : "added";
|
|
43240
|
-
} else if (codes[0] === "0" || codes[0] === "") {
|
|
43241
|
-
background = null;
|
|
43552
|
+
sendMessage(message) {
|
|
43553
|
+
if (this.socket?.readyState !== import_websocket.default.OPEN) {
|
|
43554
|
+
throw new Refusal("Godot is not connected");
|
|
43555
|
+
}
|
|
43556
|
+
this.socket.send(JSON.stringify(message));
|
|
43242
43557
|
}
|
|
43243
|
-
|
|
43244
|
-
|
|
43245
|
-
|
|
43246
|
-
|
|
43247
|
-
|
|
43248
|
-
|
|
43249
|
-
|
|
43250
|
-
|
|
43251
|
-
|
|
43252
|
-
|
|
43253
|
-
|
|
43254
|
-
|
|
43255
|
-
|
|
43256
|
-
return failed.map((entry) => {
|
|
43257
|
-
if (entry.detail === null) {
|
|
43258
|
-
return entry;
|
|
43558
|
+
startKeepalive() {
|
|
43559
|
+
this.stopKeepalive();
|
|
43560
|
+
this.pingInterval = setInterval(() => {
|
|
43561
|
+
if (!this.isConnected()) {
|
|
43562
|
+
return;
|
|
43563
|
+
}
|
|
43564
|
+
try {
|
|
43565
|
+
const ping = { type: "ping" };
|
|
43566
|
+
this.sendMessage(ping);
|
|
43567
|
+
} catch (error) {
|
|
43568
|
+
this.log("warn", `Failed to send ping: ${errorMessage(error)}`);
|
|
43569
|
+
}
|
|
43570
|
+
}, KEEPALIVE_INTERVAL_MS);
|
|
43259
43571
|
}
|
|
43260
|
-
|
|
43261
|
-
|
|
43262
|
-
|
|
43263
|
-
if (used.has(index)) {
|
|
43264
|
-
continue;
|
|
43265
|
-
}
|
|
43266
|
-
const wrong = ` but was
|
|
43267
|
-
'${diff.merged.replaceAll("\r", "")}'${diff.suffix}
|
|
43268
|
-
at ${diff.at}`;
|
|
43269
|
-
if (detail.includes(wrong)) {
|
|
43270
|
-
detail = detail.replace(wrong, () => ` but was
|
|
43271
|
-
'${diff.actual}'${diff.suffix}
|
|
43272
|
-
at ${diff.at}`);
|
|
43273
|
-
recovered = true;
|
|
43274
|
-
used.add(index);
|
|
43572
|
+
stopKeepalive() {
|
|
43573
|
+
if (!this.pingInterval) {
|
|
43574
|
+
return;
|
|
43275
43575
|
}
|
|
43576
|
+
clearInterval(this.pingInterval);
|
|
43577
|
+
this.pingInterval = null;
|
|
43276
43578
|
}
|
|
43277
|
-
|
|
43278
|
-
|
|
43279
|
-
|
|
43280
|
-
|
|
43281
|
-
|
|
43282
|
-
${note}`;
|
|
43283
|
-
}
|
|
43284
|
-
if (quoted !== null && masked(quoted[1] ?? "", quoted[2] ?? "")) {
|
|
43285
|
-
const end = quoted[0].length - `
|
|
43286
|
-
at `.length;
|
|
43287
|
-
detail = detail.slice(0, end).replaceAll(MASKED_BRACKET, "[") + detail.slice(end);
|
|
43579
|
+
belongsElsewhere(theirProject) {
|
|
43580
|
+
if (this.ownProject === null || theirProject.trim() === "") {
|
|
43581
|
+
return false;
|
|
43582
|
+
}
|
|
43583
|
+
return !isSameDirectory(this.ownProject, theirProject);
|
|
43288
43584
|
}
|
|
43289
|
-
|
|
43290
|
-
|
|
43291
|
-
}
|
|
43292
|
-
|
|
43293
|
-
|
|
43294
|
-
|
|
43295
|
-
|
|
43296
|
-
|
|
43297
|
-
|
|
43298
|
-
}
|
|
43299
|
-
function foldedReports(reports) {
|
|
43300
|
-
const first = new Map;
|
|
43301
|
-
for (const report of reports) {
|
|
43302
|
-
const key = JSON.stringify([report.kind, report.message, report.detail]);
|
|
43303
|
-
const seen = first.get(key);
|
|
43304
|
-
if (seen === undefined) {
|
|
43305
|
-
first.set(key, { report, times: 1 });
|
|
43306
|
-
} else {
|
|
43307
|
-
seen.times += 1;
|
|
43585
|
+
sendElsewhere(theirProject) {
|
|
43586
|
+
const said = `this server serves ${this.ownProject ?? ""}, and that editor has ${theirProject} open`;
|
|
43587
|
+
this.log("warn", `Turning away an editor from another project: ${said}`);
|
|
43588
|
+
const stranger = this.socket;
|
|
43589
|
+
this.socket = null;
|
|
43590
|
+
this.connectionInfo = null;
|
|
43591
|
+
this.stopKeepalive();
|
|
43592
|
+
const fits = closeReason(said, "start") === said;
|
|
43593
|
+
stranger?.close(OTHER_PROJECT_CLOSE_CODE, fits ? said : closeReason(`this server serves ${this.ownProject ?? ""}`, "end"));
|
|
43308
43594
|
}
|
|
43309
|
-
|
|
43310
|
-
|
|
43311
|
-
|
|
43312
|
-
|
|
43313
|
-
|
|
43314
|
-
}
|
|
43315
|
-
|
|
43316
|
-
|
|
43317
|
-
|
|
43318
|
-
|
|
43319
|
-
|
|
43320
|
-
|
|
43321
|
-
|
|
43322
|
-
|
|
43323
|
-
|
|
43324
|
-
|
|
43325
|
-
|
|
43326
|
-
|
|
43327
|
-
|
|
43328
|
-
|
|
43329
|
-
|
|
43330
|
-
|
|
43331
|
-
|
|
43332
|
-
|
|
43333
|
-
|
|
43334
|
-
|
|
43335
|
-
|
|
43336
|
-
}
|
|
43337
|
-
function failedWithin(failed, budget) {
|
|
43338
|
-
const sizes = failed.map((entry) => JSON.stringify(entry).length);
|
|
43339
|
-
const smallestFirst = sizes.map((_, index) => index).sort((a, b) => (sizes[a] ?? 0) - (sizes[b] ?? 0));
|
|
43340
|
-
const whole = new Set;
|
|
43341
|
-
let spent = 0;
|
|
43342
|
-
for (const index of smallestFirst) {
|
|
43343
|
-
const size = sizes[index] ?? 0;
|
|
43344
|
-
if (spent + size > budget) {
|
|
43345
|
-
break;
|
|
43595
|
+
elsewhereUnlessOneShot(theirProject, pid, openedByAServer) {
|
|
43596
|
+
const known = pid === undefined ? undefined : this.oneShots.get(pid);
|
|
43597
|
+
if (pid !== undefined && known !== undefined) {
|
|
43598
|
+
this.turnAway(pid, known);
|
|
43599
|
+
return;
|
|
43600
|
+
}
|
|
43601
|
+
const info = this.connectionInfo;
|
|
43602
|
+
const socket = this.socket;
|
|
43603
|
+
const reader = this.oneShotReader;
|
|
43604
|
+
if (pid === undefined || openedByAServer || info === null || reader === null) {
|
|
43605
|
+
this.sendElsewhere(theirProject);
|
|
43606
|
+
return;
|
|
43607
|
+
}
|
|
43608
|
+
info.identifying = true;
|
|
43609
|
+
reader(pid).catch(() => {
|
|
43610
|
+
return;
|
|
43611
|
+
}).then((option) => {
|
|
43612
|
+
if (this.socket !== socket || this.connectionInfo !== info) {
|
|
43613
|
+
return;
|
|
43614
|
+
}
|
|
43615
|
+
if (typeof option === "string") {
|
|
43616
|
+
this.oneShots.set(pid, option);
|
|
43617
|
+
this.turnAway(pid, option);
|
|
43618
|
+
return;
|
|
43619
|
+
}
|
|
43620
|
+
this.sendElsewhere(theirProject);
|
|
43621
|
+
});
|
|
43346
43622
|
}
|
|
43347
|
-
|
|
43348
|
-
|
|
43349
|
-
|
|
43350
|
-
|
|
43351
|
-
|
|
43352
|
-
|
|
43623
|
+
identify(pid) {
|
|
43624
|
+
const known = this.oneShots.get(pid);
|
|
43625
|
+
if (known !== undefined) {
|
|
43626
|
+
this.turnAway(pid, known);
|
|
43627
|
+
return;
|
|
43628
|
+
}
|
|
43629
|
+
const info = this.connectionInfo;
|
|
43630
|
+
const socket = this.socket;
|
|
43631
|
+
const reader = this.oneShotReader;
|
|
43632
|
+
if (info === null || reader === null) {
|
|
43633
|
+
return;
|
|
43634
|
+
}
|
|
43635
|
+
info.identifying = true;
|
|
43636
|
+
reader(pid).catch(() => {
|
|
43637
|
+
return;
|
|
43638
|
+
}).then((option) => {
|
|
43639
|
+
if (this.socket !== socket || this.connectionInfo !== info) {
|
|
43640
|
+
return;
|
|
43641
|
+
}
|
|
43642
|
+
if (typeof option === "string") {
|
|
43643
|
+
this.oneShots.set(pid, option);
|
|
43644
|
+
this.turnAway(pid, option);
|
|
43645
|
+
return;
|
|
43646
|
+
}
|
|
43647
|
+
info.identifying = false;
|
|
43648
|
+
this.log("info", `Godot editor identified: pid ${pid}`);
|
|
43649
|
+
this.emitBridgeEvent("godot_connected", { projectPath: info.projectPath });
|
|
43650
|
+
});
|
|
43353
43651
|
}
|
|
43354
|
-
|
|
43355
|
-
|
|
43356
|
-
|
|
43357
|
-
|
|
43358
|
-
|
|
43359
|
-
|
|
43360
|
-
|
|
43361
|
-
|
|
43362
|
-
|
|
43363
|
-
function orphansPrinted(printed) {
|
|
43364
|
-
const suites = [];
|
|
43365
|
-
let total = null;
|
|
43366
|
-
let running = null;
|
|
43367
|
-
for (const line of printed) {
|
|
43368
|
-
const started = RUNNING_SUITE.exec(line);
|
|
43369
|
-
if (started?.[1] !== undefined) {
|
|
43370
|
-
running = started[1];
|
|
43371
|
-
continue;
|
|
43652
|
+
turnAway(pid, option) {
|
|
43653
|
+
const said = `pid ${pid} was started with ${option}, which does one job and quits, so it is not an editor`;
|
|
43654
|
+
this.log("info", `Turning away an engine run: ${said}`);
|
|
43655
|
+
this.turnedAway = { pid, option, at: new Date };
|
|
43656
|
+
const run = this.socket;
|
|
43657
|
+
this.socket = null;
|
|
43658
|
+
this.connectionInfo = null;
|
|
43659
|
+
this.stopKeepalive();
|
|
43660
|
+
run?.close(ONE_SHOT_CLOSE_CODE, closeReason(said, "start"));
|
|
43372
43661
|
}
|
|
43373
|
-
|
|
43374
|
-
|
|
43375
|
-
|
|
43662
|
+
handleDisconnect(disconnectedSocket, reason) {
|
|
43663
|
+
if (disconnectedSocket && this.socket && disconnectedSocket !== this.socket) {
|
|
43664
|
+
this.log("debug", "Ignoring stale Godot socket disconnect event");
|
|
43665
|
+
return;
|
|
43666
|
+
}
|
|
43667
|
+
this.stopKeepalive();
|
|
43668
|
+
this.socket = null;
|
|
43669
|
+
this.connectionInfo = null;
|
|
43670
|
+
this.emitBridgeEvent("godot_disconnected", {});
|
|
43671
|
+
this.rejectAllPending(reason);
|
|
43672
|
+
this.resourceQueues.clear();
|
|
43376
43673
|
}
|
|
43377
|
-
|
|
43378
|
-
|
|
43379
|
-
total = orphans;
|
|
43380
|
-
running = null;
|
|
43381
|
-
continue;
|
|
43674
|
+
emitBridgeEvent(eventName, payload) {
|
|
43675
|
+
this.emit(eventName, payload);
|
|
43382
43676
|
}
|
|
43383
|
-
|
|
43384
|
-
|
|
43677
|
+
rejectAllPending(error) {
|
|
43678
|
+
for (const pending of this.pendingRequests.values()) {
|
|
43679
|
+
clearTimeout(pending.timeout);
|
|
43680
|
+
pending.reject(error);
|
|
43681
|
+
}
|
|
43682
|
+
this.pendingRequests.clear();
|
|
43385
43683
|
}
|
|
43386
|
-
|
|
43387
|
-
|
|
43388
|
-
|
|
43389
|
-
|
|
43390
|
-
|
|
43391
|
-
|
|
43392
|
-
|
|
43393
|
-
|
|
43394
|
-
|
|
43395
|
-
}
|
|
43396
|
-
|
|
43397
|
-
|
|
43398
|
-
|
|
43399
|
-
|
|
43400
|
-
|
|
43401
|
-
|
|
43402
|
-
if (started?.[1] !== undefined) {
|
|
43403
|
-
running = started[1];
|
|
43404
|
-
failed = [];
|
|
43405
|
-
continue;
|
|
43684
|
+
enqueueResourceRequest(resourceKey, task) {
|
|
43685
|
+
const previous = this.resourceQueues.get(resourceKey) ?? Promise.resolve();
|
|
43686
|
+
const taskPromise = previous.catch(() => {
|
|
43687
|
+
return;
|
|
43688
|
+
}).then(task);
|
|
43689
|
+
const tail = taskPromise.then(() => {
|
|
43690
|
+
return;
|
|
43691
|
+
}, () => {
|
|
43692
|
+
return;
|
|
43693
|
+
});
|
|
43694
|
+
this.resourceQueues.set(resourceKey, tail);
|
|
43695
|
+
return taskPromise.finally(() => {
|
|
43696
|
+
if (this.resourceQueues.get(resourceKey) === tail) {
|
|
43697
|
+
this.resourceQueues.delete(resourceKey);
|
|
43698
|
+
}
|
|
43699
|
+
});
|
|
43406
43700
|
}
|
|
43407
|
-
|
|
43408
|
-
|
|
43701
|
+
getResourceKey(args) {
|
|
43702
|
+
const scenePath = this.getStringArg(args, "scenePath") ?? this.getStringArg(args, "scene_path");
|
|
43703
|
+
if (scenePath) {
|
|
43704
|
+
return `scene:${scenePath}`;
|
|
43705
|
+
}
|
|
43706
|
+
const resourcePath = this.getStringArg(args, "resourcePath") ?? this.getStringArg(args, "resource_path");
|
|
43707
|
+
if (resourcePath) {
|
|
43708
|
+
return `resource:${resourcePath}`;
|
|
43709
|
+
}
|
|
43710
|
+
return;
|
|
43409
43711
|
}
|
|
43410
|
-
|
|
43411
|
-
|
|
43412
|
-
|
|
43413
|
-
continue;
|
|
43712
|
+
getStringArg(args, key) {
|
|
43713
|
+
const value = args[key];
|
|
43714
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
43414
43715
|
}
|
|
43415
|
-
|
|
43416
|
-
|
|
43417
|
-
|
|
43418
|
-
|
|
43419
|
-
|
|
43420
|
-
|
|
43421
|
-
|
|
43422
|
-
|
|
43423
|
-
}
|
|
43424
|
-
|
|
43716
|
+
isIncomingMessage(value) {
|
|
43717
|
+
if (!value || typeof value !== "object") {
|
|
43718
|
+
return false;
|
|
43719
|
+
}
|
|
43720
|
+
const message = value;
|
|
43721
|
+
const type = message["type"];
|
|
43722
|
+
if (type !== "tool_result" && type !== "pong" && type !== "godot_ready") {
|
|
43723
|
+
return false;
|
|
43724
|
+
}
|
|
43725
|
+
if (type === "pong") {
|
|
43726
|
+
return true;
|
|
43727
|
+
}
|
|
43728
|
+
if (type === "godot_ready") {
|
|
43729
|
+
return typeof message["project_path"] === "string";
|
|
43730
|
+
}
|
|
43731
|
+
return typeof message["id"] === "string" && typeof message["success"] === "boolean" && (message["error"] === undefined || typeof message["error"] === "string") && (message["busy"] === undefined || typeof message["busy"] === "boolean");
|
|
43425
43732
|
}
|
|
43426
|
-
|
|
43427
|
-
|
|
43428
|
-
}
|
|
43429
|
-
function testProgressMessage(printed, elapsedMs) {
|
|
43430
|
-
const { finished, running } = suitesPrinted(printed);
|
|
43431
|
-
const failing = finished.filter((suite) => suite.errors + suite.failures > 0).length;
|
|
43432
|
-
const seconds = Math.floor(elapsedMs / 1000);
|
|
43433
|
-
const took = seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
43434
|
-
const done = finished.length === 0 ? "No suite has finished" : `${finished.length === 1 ? "One suite has" : `${finished.length} suites have`} finished${failing === 0 ? "" : `, ${failing} with failures`}`;
|
|
43435
|
-
return `${done}${running === null ? "" : `; running ${running}`}; ${took} in.`;
|
|
43436
|
-
}
|
|
43437
|
-
var MalformedReportError, ENTITIES, ATTRIBUTE, HOOK_FAILURE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MOST_REPORTS_SHOWN = 20, LONGEST_DETAIL = 20000, MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary", SUITE_COUNTS, CASE_FAILED;
|
|
43438
|
-
var init_junit = __esm(() => {
|
|
43439
|
-
MalformedReportError = class MalformedReportError extends Error {
|
|
43440
|
-
constructor(message) {
|
|
43441
|
-
super(message);
|
|
43442
|
-
this.name = "MalformedReportError";
|
|
43733
|
+
log(level, message) {
|
|
43734
|
+
console.error(`[${new Date().toISOString()}] [GodotBridge:${level.toUpperCase()}] ${message}`);
|
|
43443
43735
|
}
|
|
43444
43736
|
};
|
|
43445
|
-
ENTITIES = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
|
|
43446
|
-
ATTRIBUTE = /\s*([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/y;
|
|
43447
|
-
HOOK_FAILURE = /^\s*(.*?)\s*\tat '(before|after)' in (res:\/\/.+?):(\d+)\s*$/;
|
|
43448
|
-
NOTHING_RAN = /no test cases found/i;
|
|
43449
|
-
NO_SUCH_PATH = /given directory or file does not exists:\s*(.+?)\s*$/i;
|
|
43450
|
-
ESCAPE = String.fromCharCode(27);
|
|
43451
|
-
PRINTED_ACTUAL = new RegExp(` but was\\n '((?:(?!\\tat ${LOCATION})[\\s\\S])*?)'((?:${ESCAPE}\\[[0-9;]*m)*)(${IGNORING_CASE})?((?:${ESCAPE}\\[[0-9;]*m)*)\\tat (${LOCATION})`, "g");
|
|
43452
|
-
QUOTED_EQUALITY = new RegExp(`^Expecting(?: error message)?:\\n '([\\s\\S]*)'\\n but was\\n '([\\s\\S]*)'(?:${IGNORING_CASE})?\\n\\tat `);
|
|
43453
|
-
COLOUR2 = new RegExp(`${ESCAPE}\\[([0-9;]*)m`, "g");
|
|
43454
|
-
SPELLED = {
|
|
43455
|
-
"<LF>": `
|
|
43456
|
-
`,
|
|
43457
|
-
"<CR>": "\r",
|
|
43458
|
-
"<TAB>": "\t",
|
|
43459
|
-
"<BS>": "\b",
|
|
43460
|
-
"<FF>": "\f",
|
|
43461
|
-
"<VT>": "\v",
|
|
43462
|
-
"<BEL>": "\x07",
|
|
43463
|
-
"<ESC>": ESCAPE,
|
|
43464
|
-
"<DEL>": ""
|
|
43465
|
-
};
|
|
43466
|
-
PRINTED_DICTIONARY = /^\{\n\t[\s\S]*\n {2}\}$/;
|
|
43467
|
-
RUNNING_SUITE = /Run Test Suite:\s*(res:\/\/.+?\.(?:gd|cs))(?=\s|$)/;
|
|
43468
|
-
ORPHANS = /(\d+)\s+orphans/;
|
|
43469
|
-
SUITE_COUNTS = /(\d+)\s+test cases\s*\|\s*(\d+)\s+errors\s*\|\s*(\d+)\s+failures/;
|
|
43470
|
-
CASE_FAILED = /^\s*res:\/\/.+?\.(?:gd|cs) > (.+?) (?:FAILED|ERROR)(?:\s|$)/;
|
|
43471
43737
|
});
|
|
43472
43738
|
|
|
43473
43739
|
// src/keeper-channel.ts
|
|
@@ -43537,10 +43803,10 @@ var init_keeper_channel = () => {};
|
|
|
43537
43803
|
|
|
43538
43804
|
// src/lifecycle-log.ts
|
|
43539
43805
|
import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync12 } from "node:fs";
|
|
43540
|
-
import { dirname as dirname20, join as
|
|
43806
|
+
import { dirname as dirname20, join as join29 } from "node:path";
|
|
43541
43807
|
import process11 from "node:process";
|
|
43542
43808
|
function lifecycleLogPath() {
|
|
43543
|
-
return
|
|
43809
|
+
return join29(runtimeDirectory(), "servers.log");
|
|
43544
43810
|
}
|
|
43545
43811
|
function writeLifecycle(event, fields = {}) {
|
|
43546
43812
|
const path = lifecycleLogPath();
|
|
@@ -43860,10 +44126,10 @@ var init_prompts = __esm(() => {
|
|
|
43860
44126
|
});
|
|
43861
44127
|
|
|
43862
44128
|
// src/restart-note.ts
|
|
43863
|
-
import { mkdirSync as mkdirSync15, readFileSync as
|
|
43864
|
-
import { dirname as dirname21, join as
|
|
44129
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync24, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
|
|
44130
|
+
import { dirname as dirname21, join as join30 } from "node:path";
|
|
43865
44131
|
function restartNotePath(projectPath) {
|
|
43866
|
-
return
|
|
44132
|
+
return join30(projectPath, ".godot", "gdharness-restart.json");
|
|
43867
44133
|
}
|
|
43868
44134
|
function noteRestartBegun(note) {
|
|
43869
44135
|
const path = restartNotePath(note.projectPath);
|
|
@@ -43877,7 +44143,7 @@ function noteRestartBegun(note) {
|
|
|
43877
44143
|
}
|
|
43878
44144
|
function restartOwed(projectPath) {
|
|
43879
44145
|
try {
|
|
43880
|
-
const parsed = JSON.parse(
|
|
44146
|
+
const parsed = JSON.parse(readFileSync24(restartNotePath(projectPath), "utf8"));
|
|
43881
44147
|
if (typeof parsed !== "object" || parsed === null) {
|
|
43882
44148
|
return null;
|
|
43883
44149
|
}
|
|
@@ -43903,17 +44169,17 @@ var init_restart_note = __esm(() => {
|
|
|
43903
44169
|
|
|
43904
44170
|
// src/run-record.ts
|
|
43905
44171
|
import { createHash as createHash5 } from "node:crypto";
|
|
43906
|
-
import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync13, readFileSync as
|
|
43907
|
-
import { basename as basename9, dirname as dirname22, join as
|
|
44172
|
+
import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync13, readFileSync as readFileSync25, realpathSync as realpathSync6, rmSync as rmSync14, statSync as statSync13 } from "node:fs";
|
|
44173
|
+
import { basename as basename9, dirname as dirname22, join as join31, resolve as resolve7 } from "node:path";
|
|
43908
44174
|
function runsDirectory() {
|
|
43909
|
-
return
|
|
44175
|
+
return join31(runtimeDirectory(), "runs");
|
|
43910
44176
|
}
|
|
43911
44177
|
function realAsFarAsItExists(path) {
|
|
43912
44178
|
const missing = [];
|
|
43913
44179
|
let at = path;
|
|
43914
44180
|
for (;; ) {
|
|
43915
44181
|
try {
|
|
43916
|
-
return
|
|
44182
|
+
return join31(realpathSync6.native(at), ...missing);
|
|
43917
44183
|
} catch {
|
|
43918
44184
|
const parent = dirname22(at);
|
|
43919
44185
|
if (parent === at) {
|
|
@@ -43930,10 +44196,10 @@ function projectKey(projectPath) {
|
|
|
43930
44196
|
return createHash5("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
43931
44197
|
}
|
|
43932
44198
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
43933
|
-
return
|
|
44199
|
+
return join31(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
43934
44200
|
}
|
|
43935
44201
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
43936
|
-
return
|
|
44202
|
+
return join31(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
43937
44203
|
}
|
|
43938
44204
|
function writeLatestRun(servedBy, latest) {
|
|
43939
44205
|
try {
|
|
@@ -43943,7 +44209,7 @@ function writeLatestRun(servedBy, latest) {
|
|
|
43943
44209
|
}
|
|
43944
44210
|
function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
43945
44211
|
try {
|
|
43946
|
-
const fields = JSON.parse(
|
|
44212
|
+
const fields = JSON.parse(readFileSync25(latestRunPath(servedBy, directory), "utf8"));
|
|
43947
44213
|
if (typeof fields !== "object" || fields === null) {
|
|
43948
44214
|
return null;
|
|
43949
44215
|
}
|
|
@@ -43958,20 +44224,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
43958
44224
|
}
|
|
43959
44225
|
function openTranscript(startedAt) {
|
|
43960
44226
|
mkdirSync16(runsDirectory(), { recursive: true });
|
|
43961
|
-
const path =
|
|
44227
|
+
const path = join31(runsDirectory(), `run-${startedAt}.log`);
|
|
43962
44228
|
return { path, fd: openSync3(path, "a") };
|
|
43963
44229
|
}
|
|
43964
44230
|
function profilePath(startedAt) {
|
|
43965
|
-
return
|
|
44231
|
+
return join31(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
43966
44232
|
}
|
|
43967
44233
|
function profileMarkPath(startedAt) {
|
|
43968
|
-
return
|
|
44234
|
+
return join31(runsDirectory(), `run-${startedAt}.profile-mark.json`);
|
|
43969
44235
|
}
|
|
43970
44236
|
function noteIsAbout(note, playing) {
|
|
43971
44237
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
43972
44238
|
}
|
|
43973
44239
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
43974
|
-
return
|
|
44240
|
+
return join31(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
43975
44241
|
}
|
|
43976
44242
|
function writeEditorRunNote(note) {
|
|
43977
44243
|
mkdirSync16(runsDirectory(), { recursive: true });
|
|
@@ -43982,7 +44248,7 @@ function writeEditorRunNote(note) {
|
|
|
43982
44248
|
}
|
|
43983
44249
|
function readEditorRunNote(projectPath) {
|
|
43984
44250
|
for (const directory of runtimeDirectories()) {
|
|
43985
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
44251
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join31(directory, "runs", "editor-run.json"));
|
|
43986
44252
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
43987
44253
|
return note;
|
|
43988
44254
|
}
|
|
@@ -43992,7 +44258,7 @@ function readEditorRunNote(projectPath) {
|
|
|
43992
44258
|
function editorNoteAt(path) {
|
|
43993
44259
|
let parsed;
|
|
43994
44260
|
try {
|
|
43995
|
-
parsed = JSON.parse(
|
|
44261
|
+
parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
43996
44262
|
} catch {
|
|
43997
44263
|
return null;
|
|
43998
44264
|
}
|
|
@@ -44016,7 +44282,7 @@ function readRunRecord(projectPath) {
|
|
|
44016
44282
|
if (own !== null) {
|
|
44017
44283
|
return own;
|
|
44018
44284
|
}
|
|
44019
|
-
const shared = recordAt(
|
|
44285
|
+
const shared = recordAt(join31(directory, "runs", SHARED_NOTE));
|
|
44020
44286
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
44021
44287
|
return shared;
|
|
44022
44288
|
}
|
|
@@ -44028,13 +44294,13 @@ function everyRunRecord() {
|
|
|
44028
44294
|
for (const directory of runtimeDirectories()) {
|
|
44029
44295
|
let entries;
|
|
44030
44296
|
try {
|
|
44031
|
-
entries = readdirSync13(
|
|
44297
|
+
entries = readdirSync13(join31(directory, "runs"));
|
|
44032
44298
|
} catch {
|
|
44033
44299
|
continue;
|
|
44034
44300
|
}
|
|
44035
44301
|
for (const entry of entries) {
|
|
44036
44302
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
44037
|
-
const record = recordAt(
|
|
44303
|
+
const record = recordAt(join31(directory, "runs", entry));
|
|
44038
44304
|
if (record !== null) {
|
|
44039
44305
|
found.push(record);
|
|
44040
44306
|
}
|
|
@@ -44046,7 +44312,7 @@ function everyRunRecord() {
|
|
|
44046
44312
|
function recordAt(path) {
|
|
44047
44313
|
let parsed;
|
|
44048
44314
|
try {
|
|
44049
|
-
parsed = JSON.parse(
|
|
44315
|
+
parsed = JSON.parse(readFileSync25(path, "utf8"));
|
|
44050
44316
|
} catch {
|
|
44051
44317
|
return null;
|
|
44052
44318
|
}
|
|
@@ -44099,7 +44365,7 @@ async function runningAs(pid) {
|
|
|
44099
44365
|
const began = await startedAt(pid, null);
|
|
44100
44366
|
const when = began === null ? {} : { startedAt: began };
|
|
44101
44367
|
if (process.platform === "linux") {
|
|
44102
|
-
const raw =
|
|
44368
|
+
const raw = readFileSync25(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
44103
44369
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
44104
44370
|
}
|
|
44105
44371
|
const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
|
|
@@ -44242,7 +44508,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
44242
44508
|
}
|
|
44243
44509
|
function clearRunRecord(projectPath) {
|
|
44244
44510
|
for (const directory of runtimeDirectories()) {
|
|
44245
|
-
const shared =
|
|
44511
|
+
const shared = join31(directory, "runs", SHARED_NOTE);
|
|
44246
44512
|
const sharedRecord = recordAt(shared);
|
|
44247
44513
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
44248
44514
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -44277,7 +44543,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
44277
44543
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
44278
44544
|
continue;
|
|
44279
44545
|
}
|
|
44280
|
-
const path =
|
|
44546
|
+
const path = join31(directory, entry);
|
|
44281
44547
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
44282
44548
|
continue;
|
|
44283
44549
|
}
|
|
@@ -44297,109 +44563,11 @@ var init_run_record = __esm(() => {
|
|
|
44297
44563
|
init_windows_ask();
|
|
44298
44564
|
});
|
|
44299
44565
|
|
|
44300
|
-
// src/script-origins.ts
|
|
44301
|
-
import { join as join30 } from "node:path";
|
|
44302
|
-
function unresolvedClassIn(message) {
|
|
44303
|
-
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
44304
|
-
}
|
|
44305
|
-
async function originsOf(errors, declared, check) {
|
|
44306
|
-
const listed = new Set(errors.map((error) => error.path));
|
|
44307
|
-
const origins = [];
|
|
44308
|
-
const unconfirmed = [];
|
|
44309
|
-
const asked = new Set;
|
|
44310
|
-
const waiting = errors.map((error) => unresolvedClassIn(error.message)).filter((name) => name !== null);
|
|
44311
|
-
while (waiting.length > 0 && asked.size < MOST_CHECKED) {
|
|
44312
|
-
const name = waiting.shift() ?? "";
|
|
44313
|
-
const path = declared.get(name);
|
|
44314
|
-
if (path === undefined || listed.has(path) || asked.has(path)) {
|
|
44315
|
-
continue;
|
|
44316
|
-
}
|
|
44317
|
-
asked.add(path);
|
|
44318
|
-
const checked = await check(path);
|
|
44319
|
-
const own = checked.errors.filter((error) => error.path === path);
|
|
44320
|
-
if (own.length === 0) {
|
|
44321
|
-
unconfirmed.push({
|
|
44322
|
-
path,
|
|
44323
|
-
declares: name,
|
|
44324
|
-
why: checked.failure === null ? "the engine's check of it found no error" : `the engine's check of it did not finish (${checked.failure})`
|
|
44325
|
-
});
|
|
44326
|
-
continue;
|
|
44327
|
-
}
|
|
44328
|
-
for (const error of own) {
|
|
44329
|
-
origins.push({ ...error, declares: name });
|
|
44330
|
-
const deeper = unresolvedClassIn(error.message);
|
|
44331
|
-
if (deeper !== null) {
|
|
44332
|
-
waiting.push(deeper);
|
|
44333
|
-
}
|
|
44334
|
-
}
|
|
44335
|
-
}
|
|
44336
|
-
return { origins, unconfirmed };
|
|
44337
|
-
}
|
|
44338
|
-
function reloadedAt(entry) {
|
|
44339
|
-
for (const line of entry.detail) {
|
|
44340
|
-
const at = RELOADED_AT.exec(line.trim());
|
|
44341
|
-
if (at !== null) {
|
|
44342
|
-
return { path: at[1] ?? "", line: Number(at[2]) === 0 ? null : Number(at[2]) };
|
|
44343
|
-
}
|
|
44344
|
-
}
|
|
44345
|
-
return null;
|
|
44346
|
-
}
|
|
44347
|
-
function checkWithEngine(godotPath, projectPath) {
|
|
44348
|
-
return async (path) => {
|
|
44349
|
-
const logDir = scratchDirectory("tests");
|
|
44350
|
-
try {
|
|
44351
|
-
const ran = await runEngine(godotPath, [
|
|
44352
|
-
"--headless",
|
|
44353
|
-
"--log-file",
|
|
44354
|
-
join30(logDir, "engine.log"),
|
|
44355
|
-
"--path",
|
|
44356
|
-
projectPath,
|
|
44357
|
-
"--check-only",
|
|
44358
|
-
"-s",
|
|
44359
|
-
path
|
|
44360
|
-
], { timeout: { ms: 60000, said: "minute" } });
|
|
44361
|
-
const errors = ran.log.select({ severity: "error", sinceLastCall: false, limit: 50 }).entries.flatMap((entry) => {
|
|
44362
|
-
const at = reloadedAt(entry);
|
|
44363
|
-
return at === null ? [] : [{ ...at, message: entry.text }];
|
|
44364
|
-
});
|
|
44365
|
-
return { errors, failure: ran.failure === null ? null : howItEnded(ran) };
|
|
44366
|
-
} finally {
|
|
44367
|
-
discard(logDir);
|
|
44368
|
-
}
|
|
44369
|
-
};
|
|
44370
|
-
}
|
|
44371
|
-
function withoutListed(entries, errors) {
|
|
44372
|
-
if (errors.length === 0) {
|
|
44373
|
-
return [...entries];
|
|
44374
|
-
}
|
|
44375
|
-
const listing = discoveryListing(entries.map((entry) => entry.text));
|
|
44376
|
-
const paths = new Set(errors.map((error) => error.path));
|
|
44377
|
-
return entries.filter((entry, at) => {
|
|
44378
|
-
if (listing !== null && at >= listing.from && at < listing.to) {
|
|
44379
|
-
return false;
|
|
44380
|
-
}
|
|
44381
|
-
const reloaded = reloadedAt(entry);
|
|
44382
|
-
if (reloaded !== null && errors.some((error) => error.path === reloaded.path && error.line === reloaded.line && error.message === entry.text)) {
|
|
44383
|
-
return false;
|
|
44384
|
-
}
|
|
44385
|
-
const failedToLoad = /^Failed to load script "(res:\/\/[^"]+)" with error "[^"]+"\.$/.exec(entry.text);
|
|
44386
|
-
return failedToLoad === null || !paths.has(failedToLoad[1] ?? "");
|
|
44387
|
-
});
|
|
44388
|
-
}
|
|
44389
|
-
var UNRESOLVED, MOST_CHECKED = 8, RELOADED_AT;
|
|
44390
|
-
var init_script_origins = __esm(() => {
|
|
44391
|
-
init_engine_run();
|
|
44392
|
-
init_junit();
|
|
44393
|
-
init_scratch();
|
|
44394
|
-
UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
|
|
44395
|
-
RELOADED_AT = /GDScript::reload \((res:\/\/.+):(\d+)\)$/;
|
|
44396
|
-
});
|
|
44397
|
-
|
|
44398
44566
|
// src/script-profile.ts
|
|
44399
|
-
import { readFileSync as
|
|
44567
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
44400
44568
|
function readProfileFile(path) {
|
|
44401
44569
|
try {
|
|
44402
|
-
const parsed = JSON.parse(
|
|
44570
|
+
const parsed = JSON.parse(readFileSync26(path, "utf8"));
|
|
44403
44571
|
return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
|
|
44404
44572
|
} catch {
|
|
44405
44573
|
return null;
|
|
@@ -44427,12 +44595,16 @@ function signatureParts(signature) {
|
|
|
44427
44595
|
function profiledFunctions(totals, limit, sourceOf = () => null) {
|
|
44428
44596
|
const perCallUs = perCallCostOf(totals);
|
|
44429
44597
|
const rows = rowsOf(totals, sourceOf).map((row) => withOverhead(row, perCallUs));
|
|
44430
|
-
const
|
|
44598
|
+
const called = Object.entries(totals.functions).filter(([signature, one]) => one.calls > 0 && !isCalibration(signature));
|
|
44599
|
+
const addon = called.filter(([signature]) => isRuntimeAddon(signature));
|
|
44600
|
+
const selfOf = (entries) => ms(entries.reduce((sum, [, one]) => sum + one.selfSeconds, 0));
|
|
44431
44601
|
return {
|
|
44432
44602
|
functions: rows.slice(0, limit).map((row) => placedLambda(row, sourceOf)),
|
|
44433
44603
|
omitted: Math.max(0, rows.length - limit),
|
|
44434
|
-
scriptMs,
|
|
44604
|
+
scriptMs: selfOf(called.filter(([signature]) => !isRuntimeAddon(signature))),
|
|
44435
44605
|
counted: rows.length,
|
|
44606
|
+
addonMs: selfOf(addon),
|
|
44607
|
+
addonFunctions: addon.length,
|
|
44436
44608
|
superCallers: rows.filter((row) => row.selfIncludesSuper === true).length,
|
|
44437
44609
|
perCallUs,
|
|
44438
44610
|
overheadDominated: rows.filter((row) => row.overheadMs !== undefined).length,
|
|
@@ -44440,6 +44612,9 @@ function profiledFunctions(totals, limit, sourceOf = () => null) {
|
|
|
44440
44612
|
overheadReach: rows.findLastIndex((row) => row.overheadMs !== undefined) + 1
|
|
44441
44613
|
};
|
|
44442
44614
|
}
|
|
44615
|
+
function isRuntimeAddon(signature) {
|
|
44616
|
+
return signatureParts(signature).script.startsWith(RUNTIME_ADDON2);
|
|
44617
|
+
}
|
|
44443
44618
|
function isCalibration(signature) {
|
|
44444
44619
|
const parts = signatureParts(signature);
|
|
44445
44620
|
return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
|
|
@@ -44449,7 +44624,7 @@ function writeProfileMark(path, mark) {
|
|
|
44449
44624
|
}
|
|
44450
44625
|
function readProfileMark(path) {
|
|
44451
44626
|
try {
|
|
44452
|
-
const parsed = JSON.parse(
|
|
44627
|
+
const parsed = JSON.parse(readFileSync26(path, "utf8"));
|
|
44453
44628
|
return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
|
|
44454
44629
|
} catch {
|
|
44455
44630
|
return null;
|
|
@@ -44515,7 +44690,7 @@ function rowsOf(totals, sourceOf) {
|
|
|
44515
44690
|
}
|
|
44516
44691
|
return codes.get(script) ?? null;
|
|
44517
44692
|
};
|
|
44518
|
-
return Object.entries(totals.functions).filter(([signature, sum]) => sum.calls > 0 && !isCalibration(signature)).map(([signature, sum]) => {
|
|
44693
|
+
return Object.entries(totals.functions).filter(([signature, sum]) => sum.calls > 0 && !isRuntimeAddon(signature) && !isCalibration(signature)).map(([signature, sum]) => {
|
|
44519
44694
|
const parts = signatureParts(signature);
|
|
44520
44695
|
const code = codeOfScript(parts.script);
|
|
44521
44696
|
const property = propertyOf(parts.function);
|
|
@@ -44591,7 +44766,7 @@ function callersOf(totals, name, scripts) {
|
|
|
44591
44766
|
property
|
|
44592
44767
|
};
|
|
44593
44768
|
}
|
|
44594
|
-
var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START", ms = (seconds) => Math.round(seconds * 1e6) / 1000, RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd", PROFILER_PROBE = "_profiler_probe", PROFILER_CALIBRATOR = "_calibrate_the_profiler", CALL_SITES = 40;
|
|
44769
|
+
var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START", ms = (seconds) => Math.round(seconds * 1e6) / 1000, RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd", PROFILER_PROBE = "_profiler_probe", PROFILER_CALIBRATOR = "_calibrate_the_profiler", RUNTIME_ADDON2 = "res://addons/gdharness_runtime/", CALL_SITES = 40;
|
|
44595
44770
|
var init_script_profile = __esm(() => {
|
|
44596
44771
|
init_gdscript_source();
|
|
44597
44772
|
init_godot_variant();
|
|
@@ -44622,18 +44797,18 @@ import {
|
|
|
44622
44797
|
closeSync as closeSync3,
|
|
44623
44798
|
constants,
|
|
44624
44799
|
copyFileSync,
|
|
44625
|
-
existsSync as
|
|
44800
|
+
existsSync as existsSync25,
|
|
44626
44801
|
fstatSync,
|
|
44627
44802
|
mkdirSync as mkdirSync17,
|
|
44628
44803
|
openSync as openSync4,
|
|
44629
44804
|
readdirSync as readdirSync14,
|
|
44630
|
-
readFileSync as
|
|
44805
|
+
readFileSync as readFileSync27,
|
|
44631
44806
|
readSync as readSync2,
|
|
44632
44807
|
rmdirSync as rmdirSync2,
|
|
44633
44808
|
statSync as statSync14,
|
|
44634
44809
|
writeFileSync as writeFileSync18
|
|
44635
44810
|
} from "node:fs";
|
|
44636
|
-
import { basename as basename10, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as
|
|
44811
|
+
import { basename as basename10, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as join32, normalize as normalize2, relative as relative6 } from "node:path";
|
|
44637
44812
|
import { setTimeout as delay5 } from "node:timers/promises";
|
|
44638
44813
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
44639
44814
|
function editorRestartTimeoutMs() {
|
|
@@ -44670,15 +44845,15 @@ function writtenFileRefusal(path, rule) {
|
|
|
44670
44845
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
44671
44846
|
}
|
|
44672
44847
|
const directory = realPathOr(dirname23(path));
|
|
44673
|
-
if (!
|
|
44848
|
+
if (!existsSync25(directory)) {
|
|
44674
44849
|
return `${named}'s directory ${dirname23(path)} does not exist; ${what} creates no directories.`;
|
|
44675
44850
|
}
|
|
44676
|
-
const where =
|
|
44851
|
+
const where = join32(directory, basename10(path));
|
|
44677
44852
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
44678
44853
|
if (inside !== undefined) {
|
|
44679
44854
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
44680
44855
|
}
|
|
44681
|
-
const taken = rule.writes(where).find((file) =>
|
|
44856
|
+
const taken = rule.writes(where).find((file) => existsSync25(file));
|
|
44682
44857
|
if (taken !== undefined) {
|
|
44683
44858
|
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.`;
|
|
44684
44859
|
}
|
|
@@ -45150,7 +45325,12 @@ function unlistedValues(schema, value, path) {
|
|
|
45150
45325
|
const fields = schema;
|
|
45151
45326
|
const listed = fields["enum"];
|
|
45152
45327
|
if (Array.isArray(listed) && typeof value === "string") {
|
|
45153
|
-
|
|
45328
|
+
if (listed.includes(value)) {
|
|
45329
|
+
return [];
|
|
45330
|
+
}
|
|
45331
|
+
return [
|
|
45332
|
+
listed.length === 1 ? `${path} only as ${String(listed[0])}, not ${JSON.stringify(value)}` : `${path} as one of ${listed.join(", ")}, not ${JSON.stringify(value)}`
|
|
45333
|
+
];
|
|
45154
45334
|
}
|
|
45155
45335
|
if (Array.isArray(value)) {
|
|
45156
45336
|
return value.flatMap((element, index) => unlistedValues(fields["items"], element, `${path}[${index}]`));
|
|
@@ -45214,7 +45394,7 @@ function notRunNote(notRun, failFast) {
|
|
|
45214
45394
|
function exportPresetRefusal(projectPath, preset) {
|
|
45215
45395
|
let text;
|
|
45216
45396
|
try {
|
|
45217
|
-
text =
|
|
45397
|
+
text = readFileSync27(join32(projectPath, "export_presets.cfg"), "utf8");
|
|
45218
45398
|
} catch {
|
|
45219
45399
|
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.`;
|
|
45220
45400
|
}
|
|
@@ -45280,7 +45460,7 @@ function filesIn(folder) {
|
|
|
45280
45460
|
try {
|
|
45281
45461
|
for (const entry of readdirSync14(folder, { withFileTypes: true })) {
|
|
45282
45462
|
if (entry.isFile()) {
|
|
45283
|
-
found.set(entry.name, statSync14(
|
|
45463
|
+
found.set(entry.name, statSync14(join32(folder, entry.name)).mtimeMs);
|
|
45284
45464
|
}
|
|
45285
45465
|
}
|
|
45286
45466
|
} catch {}
|
|
@@ -45310,7 +45490,7 @@ function unsavedInEditorNote(scripts) {
|
|
|
45310
45490
|
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.`;
|
|
45311
45491
|
}
|
|
45312
45492
|
function editorAddonMissing(projectPath) {
|
|
45313
|
-
if (!
|
|
45493
|
+
if (!existsSync25(join32(projectPath, "project.godot"))) {
|
|
45314
45494
|
return null;
|
|
45315
45495
|
}
|
|
45316
45496
|
const report = inspectProject(projectPath);
|
|
@@ -45476,8 +45656,35 @@ function scriptErrorsNote(errors, {
|
|
|
45476
45656
|
const opening = unnamed === 0 ? `No tests ran: gdUnit4 could not load ${scripts === 1 ? "one script" : `${scripts} scripts`} while looking for suites, and it runs no suite at all when one fails to load.` : `No tests ran: ${scripts + unnamed} scripts did not compile, and gdUnit4 runs no suite at all when one fails to load. It could not load ${scripts === 1 ? "one" : scripts} of them while looking for suites, and ${unnamed === 1 ? "the one it did not name comes" : `the ${unnamed} it did not name come`} first.`;
|
|
45477
45657
|
return [opening, named, following, rest, after].filter((part) => part !== "").join(" ");
|
|
45478
45658
|
}
|
|
45479
|
-
function
|
|
45659
|
+
function autoloadNotCompiledNote(at, errors, stoppedEarly) {
|
|
45660
|
+
const first = errors.scriptErrors[0];
|
|
45661
|
+
const where = first === undefined ? "" : `, and the first error the engine raised compiling was at ${first.path}${first.line === null ? "" : `:${first.line}`}: ${first.message}`;
|
|
45662
|
+
const others = errors.scriptErrors.length - 1;
|
|
45663
|
+
const more = others > 0 ? ` ${others === 1 ? "One more error is" : `${others} more errors are`} under scriptErrors.` : "";
|
|
45664
|
+
const through = errors.failedThroughDependency.length;
|
|
45665
|
+
const dependents = through > 0 ? ` ${through === 1 ? "One script" : `${through} scripts`} failed only through ${first === undefined ? "it" : "that"}, listed under failedThroughDependency.` : "";
|
|
45666
|
+
const ended = stoppedEarly ? "The run was stopped as soon as the engine said so rather than left to its timeout" : "The run ended before that was read, and what its suites said is set aside";
|
|
45667
|
+
const stop = /[.!?]\)?$/.test(where) ? "" : ".";
|
|
45668
|
+
return `No result counts: the autoload ${at.name} (${at.path}) did not compile${where}${stop}${more}${dependents} Every suite runs with the autoloads, so a case calling into a script that did not compile fails the same way on every pass, and one waiting in a loop for the call to succeed never ends. ${ended}. script_diagnostics checks a script without running anything.`;
|
|
45669
|
+
}
|
|
45670
|
+
function repeatingScriptError(entries, byScript) {
|
|
45671
|
+
const last = entries.at(-1);
|
|
45672
|
+
if (last === undefined || !byScript(last)) {
|
|
45673
|
+
return null;
|
|
45674
|
+
}
|
|
45675
|
+
const whole = last.detail.length > 0 ? last : entries.findLast((entry) => entry.text === last.text && entry.detail.length > 0 && byScript(entry)) ?? last;
|
|
45676
|
+
const where = whole.detail[0] ?? null;
|
|
45677
|
+
const times = entries.filter((entry) => entry.text === last.text && (entry.detail[0] ?? null) === where && byScript(entry)).length;
|
|
45678
|
+
return times >= LOOPING_AFTER ? { text: last.text, where: where?.replace(/^at: /, "") ?? null, times } : null;
|
|
45679
|
+
}
|
|
45680
|
+
function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
|
|
45480
45681
|
const quiet = Math.round(silentForMs);
|
|
45682
|
+
if (repeating !== null) {
|
|
45683
|
+
return {
|
|
45684
|
+
verdict: `timed out after ${timeoutMs} ms repeating one script error, printed ${repeating.times} times: ${repeating.text}${repeating.where === null ? "" : ` (${repeating.where})`}. A line that fails on every pass of a loop does this, and a longer timeoutMs would not end it`,
|
|
45685
|
+
hung: false
|
|
45686
|
+
};
|
|
45687
|
+
}
|
|
45481
45688
|
return quiet >= Math.min(STALLED_AFTER_MS, timeoutMs / 2) ? {
|
|
45482
45689
|
verdict: `hung: killed after ${timeoutMs} ms, having printed nothing for the last ${quiet} ms`,
|
|
45483
45690
|
hung: true
|
|
@@ -45548,7 +45755,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
45548
45755
|
let projectGodot = null;
|
|
45549
45756
|
if (projectPath !== null) {
|
|
45550
45757
|
try {
|
|
45551
|
-
projectGodot =
|
|
45758
|
+
projectGodot = readFileSync27(join32(projectPath, "project.godot"), "utf8");
|
|
45552
45759
|
} catch {}
|
|
45553
45760
|
}
|
|
45554
45761
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -45572,7 +45779,7 @@ function projectSourceOf(projectPath) {
|
|
|
45572
45779
|
let source = null;
|
|
45573
45780
|
if (inside.ok) {
|
|
45574
45781
|
try {
|
|
45575
|
-
source =
|
|
45782
|
+
source = readFileSync27(inside.absolutePath, "utf8");
|
|
45576
45783
|
} catch {}
|
|
45577
45784
|
}
|
|
45578
45785
|
read.set(script, source);
|
|
@@ -45623,6 +45830,9 @@ function profileAnswer(reading, limit) {
|
|
|
45623
45830
|
if (reading.coveredMs > 0 && table.scriptMs > reading.coveredMs) {
|
|
45624
45831
|
notes.push(`scriptMs, ${table.scriptMs}ms, is more than the ${reading.coveredMs}ms the profile covers.${table.superCallers > 0 ? " The super calls above, counted twice, can account for some or all of that; whatever they do not is" : " One thread cannot spend more time than passes, so that is"} functions running on several threads at once, through WorkerThreadPool or Thread. The engine adds every thread's time to the same function and does not say which thread a call ran on, so a function's selfMs is summed over every thread it ran on rather than taken out of frames, and a function that waits for the others, through wait_for_group_task_completion or wait_to_finish, has the wait in its own selfMs as well.`);
|
|
45625
45832
|
}
|
|
45833
|
+
if (table.addonFunctions > 0) {
|
|
45834
|
+
notes.push(`The gdharness runtime addon's own functions took ${table.addonMs}ms of self time over ${table.addonFunctions === 1 ? "one function" : `${table.addonFunctions} functions`}, answering tools such as runtime_invoke and sending the profile. They are left out of functions, functionsCounted and scriptMs and summed under runtimeAddonMs, since they are not the game's work, though that time was spent in the game's frames.`);
|
|
45835
|
+
}
|
|
45626
45836
|
notes.push(...cappedNotes(totals));
|
|
45627
45837
|
if (totals.unreadable > 0) {
|
|
45628
45838
|
notes.push(`${totals.unreadable} message${totals.unreadable === 1 ? "" : "s"} from the game could not be read, so the totals have that many gaps.`);
|
|
@@ -45637,6 +45847,8 @@ function profileAnswer(reading, limit) {
|
|
|
45637
45847
|
scriptMs: table.scriptMs,
|
|
45638
45848
|
perCallOverheadUs: table.perCallUs ?? undefined,
|
|
45639
45849
|
functionsCounted: table.counted,
|
|
45850
|
+
runtimeAddonMs: table.addonFunctions > 0 ? table.addonMs : undefined,
|
|
45851
|
+
runtimeAddonFunctions: table.addonFunctions > 0 ? table.addonFunctions : undefined,
|
|
45640
45852
|
omitted: table.omitted > 0 ? table.omitted : undefined,
|
|
45641
45853
|
functions: table.functions,
|
|
45642
45854
|
callers: reading.callersOf === undefined ? undefined : callersAnswer(totals, reading.callersOf),
|
|
@@ -45722,7 +45934,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
45722
45934
|
}
|
|
45723
45935
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
45724
45936
|
try {
|
|
45725
|
-
return settingKeys(
|
|
45937
|
+
return settingKeys(readFileSync27(join32(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
45726
45938
|
} catch {
|
|
45727
45939
|
return true;
|
|
45728
45940
|
}
|
|
@@ -45737,7 +45949,7 @@ function uncaughtNote(uncaught) {
|
|
|
45737
45949
|
}
|
|
45738
45950
|
function elsewhereIn(projectPath, asked) {
|
|
45739
45951
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
45740
|
-
if (wanted === "" ||
|
|
45952
|
+
if (wanted === "" || existsSync25(join32(projectPath, wanted))) {
|
|
45741
45953
|
return "";
|
|
45742
45954
|
}
|
|
45743
45955
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -45840,7 +46052,7 @@ function isDirectory(path) {
|
|
|
45840
46052
|
}
|
|
45841
46053
|
}
|
|
45842
46054
|
function scriptsUnder(directory, prefix) {
|
|
45843
|
-
if (
|
|
46055
|
+
if (existsSync25(join32(directory, ".gdignore"))) {
|
|
45844
46056
|
return [];
|
|
45845
46057
|
}
|
|
45846
46058
|
const found = [];
|
|
@@ -45850,29 +46062,29 @@ function scriptsUnder(directory, prefix) {
|
|
|
45850
46062
|
}
|
|
45851
46063
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
45852
46064
|
if (entry.isDirectory()) {
|
|
45853
|
-
found.push(...scriptsUnder(
|
|
46065
|
+
found.push(...scriptsUnder(join32(directory, entry.name), path));
|
|
45854
46066
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
45855
46067
|
found.push(path);
|
|
45856
46068
|
}
|
|
45857
46069
|
}
|
|
45858
46070
|
return found;
|
|
45859
46071
|
}
|
|
45860
|
-
function
|
|
45861
|
-
const scene = parseProjectGodot(
|
|
45862
|
-
return typeof scene === "string" && scene !== "";
|
|
46072
|
+
function mainSceneOf(projectFile) {
|
|
46073
|
+
const scene = parseProjectGodot(readFileSync27(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
46074
|
+
return typeof scene === "string" && scene !== "" ? scene : null;
|
|
45863
46075
|
}
|
|
45864
46076
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
45865
46077
|
if (projectPath === null) {
|
|
45866
46078
|
return false;
|
|
45867
46079
|
}
|
|
45868
46080
|
try {
|
|
45869
|
-
return
|
|
46081
|
+
return readFileSync27(join32(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
45870
46082
|
} catch {
|
|
45871
46083
|
return false;
|
|
45872
46084
|
}
|
|
45873
46085
|
}
|
|
45874
46086
|
function editorPlaysHeadless(projectFile) {
|
|
45875
|
-
const runArgs = parseProjectGodot(
|
|
46087
|
+
const runArgs = parseProjectGodot(readFileSync27(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
45876
46088
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
45877
46089
|
}
|
|
45878
46090
|
function camelCased(params) {
|
|
@@ -45891,7 +46103,7 @@ function needsItsOwnProcess(asked) {
|
|
|
45891
46103
|
class GodotServer {
|
|
45892
46104
|
stdio = null;
|
|
45893
46105
|
locator = new GodotLocator;
|
|
45894
|
-
operationsScript =
|
|
46106
|
+
operationsScript = join32(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
45895
46107
|
godotBridge;
|
|
45896
46108
|
tools = buildToolDefinitions();
|
|
45897
46109
|
activeProcess = null;
|
|
@@ -46152,14 +46364,14 @@ class GodotServer {
|
|
|
46152
46364
|
}
|
|
46153
46365
|
noteTheStart() {
|
|
46154
46366
|
const cwd = process.cwd();
|
|
46155
|
-
const mcpJson =
|
|
46367
|
+
const mcpJson = join32(cwd, ".mcp.json");
|
|
46156
46368
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
46157
46369
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
46158
46370
|
writeLifecycle("started", {
|
|
46159
46371
|
argv: process.argv.slice(1),
|
|
46160
46372
|
cwd,
|
|
46161
46373
|
project: this.ownProject,
|
|
46162
|
-
mcpJson:
|
|
46374
|
+
mcpJson: existsSync25(mcpJson) ? mcpJson : null
|
|
46163
46375
|
});
|
|
46164
46376
|
if (predecessor !== null) {
|
|
46165
46377
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -46300,7 +46512,7 @@ class GodotServer {
|
|
|
46300
46512
|
return checked.response;
|
|
46301
46513
|
}
|
|
46302
46514
|
const named = args["projectPath"];
|
|
46303
|
-
if (typeof named === "string" &&
|
|
46515
|
+
if (typeof named === "string" && existsSync25(join32(named, "project.godot"))) {
|
|
46304
46516
|
this.lastProjectPath = named;
|
|
46305
46517
|
}
|
|
46306
46518
|
this.updates.refresh();
|
|
@@ -46336,7 +46548,7 @@ class GodotServer {
|
|
|
46336
46548
|
let notSaved;
|
|
46337
46549
|
try {
|
|
46338
46550
|
savedAt = answer.content.map((item, index) => {
|
|
46339
|
-
const file =
|
|
46551
|
+
const file = join32(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
|
|
46340
46552
|
writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
|
|
46341
46553
|
return file;
|
|
46342
46554
|
});
|
|
@@ -46954,8 +47166,8 @@ class GodotServer {
|
|
|
46954
47166
|
if (path === undefined) {
|
|
46955
47167
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
46956
47168
|
}
|
|
46957
|
-
const file =
|
|
46958
|
-
if (!
|
|
47169
|
+
const file = join32(path, "project.godot");
|
|
47170
|
+
if (!existsSync25(file)) {
|
|
46959
47171
|
return {
|
|
46960
47172
|
ok: false,
|
|
46961
47173
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -47615,7 +47827,7 @@ class GodotServer {
|
|
|
47615
47827
|
if (!engine.ok) {
|
|
47616
47828
|
return engine.response;
|
|
47617
47829
|
}
|
|
47618
|
-
const application = parseProjectGodot(
|
|
47830
|
+
const application = parseProjectGodot(readFileSync27(project.value.file, "utf8"))["application"] ?? {};
|
|
47619
47831
|
const name = application["config/name"];
|
|
47620
47832
|
const mainScene = application["run/main_scene"];
|
|
47621
47833
|
const info = {
|
|
@@ -47713,7 +47925,7 @@ class GodotServer {
|
|
|
47713
47925
|
const exportArgs = [
|
|
47714
47926
|
"--headless",
|
|
47715
47927
|
"--log-file",
|
|
47716
|
-
|
|
47928
|
+
join32(exportLogs, "engine.log"),
|
|
47717
47929
|
"--path",
|
|
47718
47930
|
project.value.path,
|
|
47719
47931
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -47721,7 +47933,7 @@ class GodotServer {
|
|
|
47721
47933
|
output.absolutePath
|
|
47722
47934
|
];
|
|
47723
47935
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
47724
|
-
const writtenAt = () =>
|
|
47936
|
+
const writtenAt = () => existsSync25(output.absolutePath) ? statSync14(output.absolutePath).mtimeMs : null;
|
|
47725
47937
|
const caches = editorCacheDirectory(engine.value);
|
|
47726
47938
|
const exportOnce = async () => {
|
|
47727
47939
|
const before = writtenAt();
|
|
@@ -47804,10 +48016,10 @@ class GodotServer {
|
|
|
47804
48016
|
if (movie !== null) {
|
|
47805
48017
|
return this.createErrorResponse(movie);
|
|
47806
48018
|
}
|
|
47807
|
-
const missing = paths.value.filter((path) => !
|
|
48019
|
+
const missing = paths.value.filter((path) => !existsSync25(join32(project.value.path, path.slice("res://".length))));
|
|
47808
48020
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
47809
48021
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
47810
|
-
const noRunner = !
|
|
48022
|
+
const noRunner = !existsSync25(join32(project.value.path, runner));
|
|
47811
48023
|
if (asked.length === 0) {
|
|
47812
48024
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
47813
48025
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -47839,6 +48051,7 @@ class GodotServer {
|
|
|
47839
48051
|
if (!engine.ok) {
|
|
47840
48052
|
return engine.response;
|
|
47841
48053
|
}
|
|
48054
|
+
const began = Date.now();
|
|
47842
48055
|
const classes = await this.rebuildClassCache(project.value.path);
|
|
47843
48056
|
if (!classes.ok) {
|
|
47844
48057
|
return this.answer(classes);
|
|
@@ -47877,35 +48090,51 @@ class GodotServer {
|
|
|
47877
48090
|
lastSaid = Date.now();
|
|
47878
48091
|
});
|
|
47879
48092
|
const reporting = this.reportTestProgress(run.log);
|
|
48093
|
+
const autoloaded = scriptsAutoloaded(project.value.path);
|
|
47880
48094
|
let silentForMs = 0;
|
|
48095
|
+
const stopped = { for: null };
|
|
47881
48096
|
const timedOut = await new Promise((resolve) => {
|
|
48097
|
+
let read = 0;
|
|
48098
|
+
const watching = setInterval(() => {
|
|
48099
|
+
const entries = run.log.all;
|
|
48100
|
+
for (const entry of entries.slice(read)) {
|
|
48101
|
+
stopped.for ??= autoloadNotCompiled(entry, autoloaded);
|
|
48102
|
+
}
|
|
48103
|
+
read = entries.length;
|
|
48104
|
+
if (stopped.for !== null) {
|
|
48105
|
+
clearInterval(watching);
|
|
48106
|
+
run.process.kill();
|
|
48107
|
+
}
|
|
48108
|
+
}, 100);
|
|
47882
48109
|
const timer = setTimeout(() => {
|
|
48110
|
+
clearInterval(watching);
|
|
47883
48111
|
silentForMs = Date.now() - lastSaid;
|
|
47884
48112
|
run.process.kill();
|
|
47885
48113
|
resolve(true);
|
|
47886
48114
|
}, timeoutMs);
|
|
47887
|
-
|
|
48115
|
+
const ended = () => {
|
|
48116
|
+
clearInterval(watching);
|
|
47888
48117
|
clearTimeout(timer);
|
|
47889
48118
|
resolve(false);
|
|
47890
|
-
}
|
|
47891
|
-
run.process.once("
|
|
47892
|
-
|
|
47893
|
-
resolve(false);
|
|
47894
|
-
});
|
|
48119
|
+
};
|
|
48120
|
+
run.process.once("exit", ended);
|
|
48121
|
+
run.process.once("error", ended);
|
|
47895
48122
|
});
|
|
47896
48123
|
clearInterval(reporting);
|
|
47897
|
-
if (timedOut && run.process.pid !== undefined) {
|
|
48124
|
+
if ((timedOut || stopped.for !== null) && run.process.pid !== undefined) {
|
|
47898
48125
|
await untilGone([run.process.pid]);
|
|
47899
48126
|
}
|
|
47900
48127
|
await run.settled;
|
|
47901
|
-
const
|
|
48128
|
+
const tookMs = Date.now() - began;
|
|
48129
|
+
const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
|
|
48130
|
+
const reportsDir = join32(project.value.path, ".godot", ...ours.split("/"));
|
|
47902
48131
|
let report = null;
|
|
47903
48132
|
let reportProblem = null;
|
|
47904
48133
|
try {
|
|
47905
|
-
const written =
|
|
48134
|
+
const written = existsSync25(reportsDir) && notCompiled === null ? readdirSync14(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
47906
48135
|
const newest = written.at(-1);
|
|
47907
48136
|
if (newest !== undefined) {
|
|
47908
|
-
report = parseJUnit(
|
|
48137
|
+
report = parseJUnit(readFileSync27(join32(reportsDir, newest, "results.xml"), "utf8"));
|
|
47909
48138
|
}
|
|
47910
48139
|
} catch (error) {
|
|
47911
48140
|
reportProblem = errorMessage(error);
|
|
@@ -47937,14 +48166,14 @@ class GodotServer {
|
|
|
47937
48166
|
limit: Number.POSITIVE_INFINITY
|
|
47938
48167
|
}).entries;
|
|
47939
48168
|
const said = printed.map((entry) => entry.text);
|
|
47940
|
-
const nothingRan = timedOut ? null : whyNoReport(said, asked.join(" and "));
|
|
47941
|
-
const cutShort = timedOut ? timedOutVerdict(timeoutMs, silentForMs) : null;
|
|
48169
|
+
const nothingRan = timedOut || notCompiled !== null ? null : whyNoReport(said, asked.join(" and "));
|
|
48170
|
+
const cutShort = timedOut && notCompiled === null ? timedOutVerdict(timeoutMs, silentForMs, repeatingScriptError(run.log.all, (entry) => run.log.raisedByScript(entry))) : null;
|
|
47942
48171
|
const hung = cutShort?.hung ?? false;
|
|
47943
|
-
const printedErrors = timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
|
|
48172
|
+
const printedErrors = notCompiled !== null ? compileErrorsIn(run.log.all) : timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
|
|
47944
48173
|
const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
|
|
47945
48174
|
const scriptErrors = [...declaring.origins, ...printedErrors];
|
|
47946
48175
|
const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
|
|
47947
|
-
const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? howItExited(run);
|
|
48176
|
+
const ranAs = notCompiled !== null ? `the autoload ${notCompiled.name} (${notCompiled.path}) did not compile, so no result counts` : cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? howItExited(run);
|
|
47948
48177
|
const nothingAt = missing.join(" and ");
|
|
47949
48178
|
const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
|
|
47950
48179
|
const missingNote = missing.length === 0 ? undefined : `Nothing at ${nothingAt}, so ${missing.length === 1 ? "it was" : "they were"} left out and only ${asked.join(", ")} ran; a run of fewer paths than were asked for is not called a pass.${elsewhereIn(project.value.path, missing[0] ?? "")}`;
|
|
@@ -47972,7 +48201,7 @@ class GodotServer {
|
|
|
47972
48201
|
}),
|
|
47973
48202
|
...running === null ? {} : { suiteRunning: running }
|
|
47974
48203
|
};
|
|
47975
|
-
const said = nothingRan !== null ? `No tests ran: ${ranAs}.` : scriptErrors.length > 0 ? scriptErrorsNote(printedErrors, declaring) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${ranAs}.` : `The test run wrote no report (${ranAs}).${finishedBeforeTheEnd(finished.length, failing.length, running)}`;
|
|
48204
|
+
const said = notCompiled !== null ? autoloadNotCompiledNote(notCompiled, foldedScriptErrors(scriptErrors), stopped.for !== null) : nothingRan !== null ? `No tests ran: ${ranAs}.` : scriptErrors.length > 0 ? scriptErrorsNote(printedErrors, declaring) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${ranAs}.` : `The test run wrote no report (${ranAs}).${finishedBeforeTheEnd(finished.length, failing.length, running)}`;
|
|
47976
48205
|
const note = missingNote === undefined ? said : `${said} ${missingNote}`;
|
|
47977
48206
|
if (callSignal()?.aborted === true) {
|
|
47978
48207
|
keepCutTestRun(project.value.path, { cutAt: new Date().toISOString(), paths: asked, ...atTheEnd });
|
|
@@ -47986,6 +48215,7 @@ class GodotServer {
|
|
|
47986
48215
|
passed: false,
|
|
47987
48216
|
verdict,
|
|
47988
48217
|
tests: 0,
|
|
48218
|
+
tookMs,
|
|
47989
48219
|
exitCode,
|
|
47990
48220
|
exitSignal: run.exitSignal ?? undefined,
|
|
47991
48221
|
hung,
|
|
@@ -48043,6 +48273,7 @@ class GodotServer {
|
|
|
48043
48273
|
errors: report.errors,
|
|
48044
48274
|
skipped: report.skipped,
|
|
48045
48275
|
time: report.time,
|
|
48276
|
+
tookMs,
|
|
48046
48277
|
failed,
|
|
48047
48278
|
failedWithoutDetail: briefed > 0 ? briefed : undefined,
|
|
48048
48279
|
hookFailures: hookFailures.length > 0 ? hookFailures : undefined,
|
|
@@ -48271,11 +48502,11 @@ class GodotServer {
|
|
|
48271
48502
|
const cached = cachedClasses(projectPath);
|
|
48272
48503
|
const sourceOf = (resourcePath) => {
|
|
48273
48504
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
48274
|
-
if (!contained.ok || !
|
|
48505
|
+
if (!contained.ok || !existsSync25(contained.absolutePath)) {
|
|
48275
48506
|
return null;
|
|
48276
48507
|
}
|
|
48277
48508
|
try {
|
|
48278
|
-
return
|
|
48509
|
+
return readFileSync27(contained.absolutePath, "utf8");
|
|
48279
48510
|
} catch {
|
|
48280
48511
|
return null;
|
|
48281
48512
|
}
|
|
@@ -48365,11 +48596,11 @@ class GodotServer {
|
|
|
48365
48596
|
}
|
|
48366
48597
|
sourceOfClass(projectPath, resourcePath) {
|
|
48367
48598
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
48368
|
-
if (!contained.ok || !
|
|
48599
|
+
if (!contained.ok || !existsSync25(contained.absolutePath)) {
|
|
48369
48600
|
return null;
|
|
48370
48601
|
}
|
|
48371
48602
|
try {
|
|
48372
|
-
return
|
|
48603
|
+
return readFileSync27(contained.absolutePath, "utf8");
|
|
48373
48604
|
} catch {
|
|
48374
48605
|
return null;
|
|
48375
48606
|
}
|
|
@@ -48527,7 +48758,7 @@ class GodotServer {
|
|
|
48527
48758
|
...Array.isArray(stack) ? {
|
|
48528
48759
|
stack: stack.map((frame) => ({
|
|
48529
48760
|
...frame,
|
|
48530
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
48761
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join32(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
48531
48762
|
}))
|
|
48532
48763
|
} : {}
|
|
48533
48764
|
});
|
|
@@ -48806,7 +49037,7 @@ class GodotServer {
|
|
|
48806
49037
|
return null;
|
|
48807
49038
|
}
|
|
48808
49039
|
try {
|
|
48809
|
-
return settingKeys(
|
|
49040
|
+
return settingKeys(readFileSync27(join32(projectPath, "project.godot"), "utf8"));
|
|
48810
49041
|
} catch {
|
|
48811
49042
|
return null;
|
|
48812
49043
|
}
|
|
@@ -49149,7 +49380,7 @@ class GodotServer {
|
|
|
49149
49380
|
if (!asked.ok) {
|
|
49150
49381
|
return asked.response;
|
|
49151
49382
|
}
|
|
49152
|
-
if (exported === null && !sceneToRun &&
|
|
49383
|
+
if (exported === null && !sceneToRun && mainSceneOf(project.value.file) === null) {
|
|
49153
49384
|
return this.createErrorResponse("The project sets no main scene, so there is nothing to run.", [
|
|
49154
49385
|
"Pass scene to run one scene",
|
|
49155
49386
|
"Choose the main scene with project_settings set_main_scene"
|
|
@@ -49226,6 +49457,7 @@ class GodotServer {
|
|
|
49226
49457
|
alsoRunning: stranded.length === 0 ? undefined : stranded.map((one) => one.pid),
|
|
49227
49458
|
pid: started.pid,
|
|
49228
49459
|
executable: exported?.ok === true ? exported.relativePath : undefined,
|
|
49460
|
+
scene: exported !== null ? undefined : sceneArgument === null ? mainSceneOf(project.value.file) : `res://${sceneArgument}`,
|
|
49229
49461
|
arguments: cmdArgs,
|
|
49230
49462
|
argsNote: engineFlagsNote(given.value),
|
|
49231
49463
|
transcript: started.transcript,
|
|
@@ -49243,7 +49475,7 @@ class GodotServer {
|
|
|
49243
49475
|
}
|
|
49244
49476
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
49245
49477
|
const budgetMs = budget.ms;
|
|
49246
|
-
if (!
|
|
49478
|
+
if (!existsSync25(join32(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
49247
49479
|
return runtimeVerdict(null, {
|
|
49248
49480
|
addon: false,
|
|
49249
49481
|
budgetMs,
|
|
@@ -49523,7 +49755,7 @@ class GodotServer {
|
|
|
49523
49755
|
return 0;
|
|
49524
49756
|
}
|
|
49525
49757
|
try {
|
|
49526
|
-
return reportAlreadyInTranscript(
|
|
49758
|
+
return reportAlreadyInTranscript(readFileSync27(game.transcript), readFileSync27(report));
|
|
49527
49759
|
} catch (error) {
|
|
49528
49760
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
49529
49761
|
return 0;
|
|
@@ -50225,7 +50457,7 @@ class GodotServer {
|
|
|
50225
50457
|
};
|
|
50226
50458
|
const going = await this.runStillGoing(run);
|
|
50227
50459
|
const projectPath = run.projectPath;
|
|
50228
|
-
const projectGone = projectPath !== null && !
|
|
50460
|
+
const projectGone = projectPath !== null && !existsSync25(join32(projectPath, "project.godot"));
|
|
50229
50461
|
if (projectGone && callersName !== undefined) {
|
|
50230
50462
|
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"]);
|
|
50231
50463
|
}
|
|
@@ -50572,7 +50804,7 @@ class GodotServer {
|
|
|
50572
50804
|
notes.push(endedWithoutACode({
|
|
50573
50805
|
throughEditor: run.throughEditor,
|
|
50574
50806
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
50575
|
-
couldAnnounce: run.projectPath !== null &&
|
|
50807
|
+
couldAnnounce: run.projectPath !== null && existsSync25(join32(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
50576
50808
|
endedHere: typeof run.endedHere === "string",
|
|
50577
50809
|
pickedUp: run.pickedUp === true
|
|
50578
50810
|
}));
|
|
@@ -51202,7 +51434,7 @@ class GodotServer {
|
|
|
51202
51434
|
if (!scriptPath.ok) {
|
|
51203
51435
|
return this.createErrorResponse(scriptPath.reason);
|
|
51204
51436
|
}
|
|
51205
|
-
if (!
|
|
51437
|
+
if (!existsSync25(scriptPath.absolutePath)) {
|
|
51206
51438
|
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
51207
51439
|
}
|
|
51208
51440
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
@@ -51616,7 +51848,7 @@ class GodotServer {
|
|
|
51616
51848
|
}
|
|
51617
51849
|
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
51618
51850
|
const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
|
|
51619
|
-
if (declared.test(
|
|
51851
|
+
if (declared.test(readFileSync27(scriptPath, "utf8"))) {
|
|
51620
51852
|
return null;
|
|
51621
51853
|
}
|
|
51622
51854
|
const checked = await this.classesTheEditorCannotSee(args);
|
|
@@ -51784,7 +52016,7 @@ class GodotServer {
|
|
|
51784
52016
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
51785
52017
|
const run = await this.currentRun();
|
|
51786
52018
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
51787
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
52019
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync25(join32(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
51788
52020
|
return null;
|
|
51789
52021
|
}
|
|
51790
52022
|
const already = runtimesAnnounced().running;
|
|
@@ -51874,7 +52106,7 @@ class GodotServer {
|
|
|
51874
52106
|
}
|
|
51875
52107
|
}
|
|
51876
52108
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
51877
|
-
const screenshotPath = screenshotDir ?
|
|
52109
|
+
const screenshotPath = screenshotDir ? join32(screenshotDir, "capture.png") : null;
|
|
51878
52110
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
51879
52111
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
51880
52112
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|
|
@@ -51893,8 +52125,10 @@ ${staleNote}`);
|
|
|
51893
52125
|
}
|
|
51894
52126
|
this.callsOnGames.add(onGame);
|
|
51895
52127
|
const alongside = () => onGame.alongside.size === 0 ? {} : { ranAlongside: [...onGame.alongside], alongsideNote: ALONGSIDE_NOTE };
|
|
52128
|
+
let savedLater = false;
|
|
51896
52129
|
try {
|
|
51897
|
-
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs,
|
|
52130
|
+
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, expectsScreenshot ? () => discard(screenshotDir) : "keep");
|
|
52131
|
+
savedLater = !reply.ok && reply.stillWithTheGame === true;
|
|
51898
52132
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
51899
52133
|
return this.jsonTextResponse({
|
|
51900
52134
|
pending: true,
|
|
@@ -51940,12 +52174,14 @@ Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}${staleNote === undefine
|
|
|
51940
52174
|
|
|
51941
52175
|
${staleNote}`}`
|
|
51942
52176
|
},
|
|
51943
|
-
{ type: "image", data:
|
|
52177
|
+
{ type: "image", data: readFileSync27(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
51944
52178
|
]
|
|
51945
52179
|
};
|
|
51946
52180
|
} finally {
|
|
51947
52181
|
this.callsOnGames.delete(onGame);
|
|
51948
|
-
|
|
52182
|
+
if (!savedLater) {
|
|
52183
|
+
discard(screenshotDir);
|
|
52184
|
+
}
|
|
51949
52185
|
}
|
|
51950
52186
|
}
|
|
51951
52187
|
handleLateAnswer(args) {
|
|
@@ -52055,12 +52291,13 @@ async function runGodotServer() {
|
|
|
52055
52291
|
const server = new GodotServer;
|
|
52056
52292
|
await server.run();
|
|
52057
52293
|
}
|
|
52058
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
52294
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
52059
52295
|
var init_server = __esm(() => {
|
|
52060
52296
|
init_dist();
|
|
52061
52297
|
init_stdio();
|
|
52062
52298
|
init_alive();
|
|
52063
52299
|
init_answer_json();
|
|
52300
|
+
init_autoload_failure();
|
|
52064
52301
|
init_boot_note();
|
|
52065
52302
|
init_breakpoint_note();
|
|
52066
52303
|
init_bridge_announce();
|
|
@@ -52174,8 +52411,8 @@ var init_server = __esm(() => {
|
|
|
52174
52411
|
init_bridge_announce();
|
|
52175
52412
|
init_errors();
|
|
52176
52413
|
init_godot_path();
|
|
52177
|
-
import { existsSync as
|
|
52178
|
-
import { join as
|
|
52414
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
52415
|
+
import { join as join33, resolve as resolve8 } from "node:path";
|
|
52179
52416
|
|
|
52180
52417
|
// src/harnesses.ts
|
|
52181
52418
|
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";
|
|
@@ -53141,9 +53378,9 @@ init_setup();
|
|
|
53141
53378
|
init_tool_definitions();
|
|
53142
53379
|
init_tool_reference();
|
|
53143
53380
|
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
53144
|
-
import { basename as basename6, dirname as dirname8, join as
|
|
53381
|
+
import { basename as basename6, dirname as dirname8, join as join15 } from "node:path";
|
|
53145
53382
|
var SKILL_NAME = "gdharness";
|
|
53146
|
-
var SHARED_SKILLS =
|
|
53383
|
+
var SHARED_SKILLS = join15(".agents", "skills");
|
|
53147
53384
|
function skillMarkdown(version) {
|
|
53148
53385
|
return `---
|
|
53149
53386
|
name: ${SKILL_NAME}
|
|
@@ -53368,27 +53605,27 @@ what they report while starting comes back under \`engine_messages_before_operat
|
|
|
53368
53605
|
function skillFiles(version) {
|
|
53369
53606
|
return new Map([
|
|
53370
53607
|
["SKILL.md", skillMarkdown(version)],
|
|
53371
|
-
[
|
|
53608
|
+
[join15("references", "tools.md"), renderToolsMarkdown()]
|
|
53372
53609
|
]);
|
|
53373
53610
|
}
|
|
53374
53611
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
53375
|
-
const directories = [
|
|
53612
|
+
const directories = [join15(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
53376
53613
|
for (const harness of harnesses) {
|
|
53377
53614
|
if (harness.skills === undefined) {
|
|
53378
53615
|
continue;
|
|
53379
53616
|
}
|
|
53380
|
-
const own =
|
|
53381
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
53382
|
-
directories.push(
|
|
53617
|
+
const own = join15(projectPath, harness.skills.dir);
|
|
53618
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join15(own, SKILL_NAME))) {
|
|
53619
|
+
directories.push(join15(own, SKILL_NAME));
|
|
53383
53620
|
}
|
|
53384
53621
|
}
|
|
53385
53622
|
return directories;
|
|
53386
53623
|
}
|
|
53387
53624
|
function everySkillDirectory(projectPath, harnesses) {
|
|
53388
|
-
const directories = new Set([
|
|
53625
|
+
const directories = new Set([join15(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
53389
53626
|
for (const harness of harnesses) {
|
|
53390
53627
|
if (harness.skills !== undefined) {
|
|
53391
|
-
directories.add(
|
|
53628
|
+
directories.add(join15(projectPath, harness.skills.dir, SKILL_NAME));
|
|
53392
53629
|
}
|
|
53393
53630
|
}
|
|
53394
53631
|
return [...directories];
|
|
@@ -53405,7 +53642,7 @@ function removeSkill(directories, projectPath) {
|
|
|
53405
53642
|
return removed;
|
|
53406
53643
|
}
|
|
53407
53644
|
function pruneEmpty(directory, projectPath) {
|
|
53408
|
-
const shared =
|
|
53645
|
+
const shared = join15(projectPath, ".agents");
|
|
53409
53646
|
let path = directory;
|
|
53410
53647
|
while (path !== projectPath && (basename6(path) === "skills" || path === shared)) {
|
|
53411
53648
|
if (readdirSync9(path).length > 0) {
|
|
@@ -53421,7 +53658,7 @@ function writeSkill(directories, version) {
|
|
|
53421
53658
|
const replaced = existsSync14(directory);
|
|
53422
53659
|
rmSync6(directory, { recursive: true, force: true });
|
|
53423
53660
|
for (const [name, contents] of files) {
|
|
53424
|
-
const path =
|
|
53661
|
+
const path = join15(directory, name);
|
|
53425
53662
|
mkdirSync5(dirname8(path), { recursive: true });
|
|
53426
53663
|
writeFileSync7(path, contents, "utf8");
|
|
53427
53664
|
}
|
|
@@ -53431,13 +53668,13 @@ function writeSkill(directories, version) {
|
|
|
53431
53668
|
|
|
53432
53669
|
// src/version.ts
|
|
53433
53670
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
53434
|
-
import { dirname as dirname9, join as
|
|
53671
|
+
import { dirname as dirname9, join as join16 } from "node:path";
|
|
53435
53672
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
53436
53673
|
function getLocalVersion() {
|
|
53437
53674
|
const currentDirectory = dirname9(fileURLToPath2(import.meta.url));
|
|
53438
53675
|
const packageCandidates = [
|
|
53439
|
-
|
|
53440
|
-
|
|
53676
|
+
join16(currentDirectory, "..", "package.json"),
|
|
53677
|
+
join16(currentDirectory, "..", "..", "package.json")
|
|
53441
53678
|
];
|
|
53442
53679
|
for (const packagePath of packageCandidates) {
|
|
53443
53680
|
try {
|
|
@@ -53459,7 +53696,7 @@ class UsageError extends Refusal {
|
|
|
53459
53696
|
function projectArgument(at) {
|
|
53460
53697
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
53461
53698
|
const projectPath = resolve8(named ?? ".");
|
|
53462
|
-
if (!
|
|
53699
|
+
if (!existsSync26(join33(projectPath, "project.godot"))) {
|
|
53463
53700
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
53464
53701
|
}
|
|
53465
53702
|
return projectPath;
|
|
@@ -53504,7 +53741,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
53504
53741
|
return;
|
|
53505
53742
|
}
|
|
53506
53743
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
53507
|
-
if (!
|
|
53744
|
+
if (!existsSync26(join33(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
53508
53745
|
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}`);
|
|
53509
53746
|
}
|
|
53510
53747
|
}
|
|
@@ -53636,7 +53873,7 @@ async function setup() {
|
|
|
53636
53873
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
53637
53874
|
}
|
|
53638
53875
|
if (!args.includes("--no-skill")) {
|
|
53639
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
53876
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync26);
|
|
53640
53877
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
53641
53878
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
53642
53879
|
}
|
|
@@ -53750,7 +53987,7 @@ async function upgrade() {
|
|
|
53750
53987
|
moved.push(took.from);
|
|
53751
53988
|
}
|
|
53752
53989
|
}
|
|
53753
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
53990
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync26), version)) {
|
|
53754
53991
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
53755
53992
|
}
|
|
53756
53993
|
console.log(`
|