gdharness 0.5.0 → 0.5.1
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 +282 -246
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +9 -1
- package/build/index.js +260 -224
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -9826,17 +9826,213 @@ function toError(error) {
|
|
|
9826
9826
|
}
|
|
9827
9827
|
|
|
9828
9828
|
// src/server-version.ts
|
|
9829
|
-
import { readFileSync } from "node:fs";
|
|
9829
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
9830
|
+
|
|
9831
|
+
// src/update-check.ts
|
|
9832
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9833
|
+
import { homedir, tmpdir } from "node:os";
|
|
9834
|
+
import { join } from "node:path";
|
|
9835
|
+
|
|
9836
|
+
// src/runner.ts
|
|
9837
|
+
function currentRunner() {
|
|
9838
|
+
const versions = process.versions;
|
|
9839
|
+
return versions["bun"] === undefined ? "npx" : "bunx";
|
|
9840
|
+
}
|
|
9841
|
+
function runLine(runner, version, rest = "") {
|
|
9842
|
+
const flag = runner === "npx" ? "-y " : "";
|
|
9843
|
+
return `${runner} ${flag}gdharness@${version}${rest === "" ? "" : ` ${rest}`}`;
|
|
9844
|
+
}
|
|
9845
|
+
|
|
9846
|
+
// src/update-check.ts
|
|
9847
|
+
var REGISTRY = "https://registry.npmjs.org/gdharness/latest";
|
|
9848
|
+
var RELEASES = "https://github.com/Aureliolo/gdharness/releases/tag";
|
|
9849
|
+
var CACHE_MS = 4 * 60 * 60 * 1000;
|
|
9850
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
9851
|
+
var MAX_BODY_BYTES = 1 << 20;
|
|
9852
|
+
var FIRST_RETRY_MS = 30000;
|
|
9853
|
+
var MAX_RETRY_MS = 60 * 60 * 1000;
|
|
9854
|
+
var VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
9855
|
+
function cacheDirectory(environment) {
|
|
9856
|
+
const set = (name) => {
|
|
9857
|
+
const value = environment[name];
|
|
9858
|
+
return value !== undefined && value !== "" ? value : null;
|
|
9859
|
+
};
|
|
9860
|
+
const home = set("HOME") ?? homedir();
|
|
9861
|
+
if (process.platform === "win32") {
|
|
9862
|
+
return join(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
9863
|
+
}
|
|
9864
|
+
if (process.platform === "darwin") {
|
|
9865
|
+
return join(home, "Library", "Caches", "gdharness");
|
|
9866
|
+
}
|
|
9867
|
+
return join(set("XDG_CACHE_HOME") ?? join(home, ".cache"), "gdharness");
|
|
9868
|
+
}
|
|
9869
|
+
function cacheFile(environment = process.env) {
|
|
9870
|
+
try {
|
|
9871
|
+
const directory = cacheDirectory(environment);
|
|
9872
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
9873
|
+
return join(directory, "update-check.json");
|
|
9874
|
+
} catch {
|
|
9875
|
+
return join(tmpdir(), "gdharness-update-check.json");
|
|
9876
|
+
}
|
|
9877
|
+
}
|
|
9878
|
+
function readCache(path) {
|
|
9879
|
+
try {
|
|
9880
|
+
if (!existsSync(path)) {
|
|
9881
|
+
return null;
|
|
9882
|
+
}
|
|
9883
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
9884
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
9885
|
+
return null;
|
|
9886
|
+
}
|
|
9887
|
+
const record = parsed;
|
|
9888
|
+
const checkedAt = record["checkedAt"];
|
|
9889
|
+
const latest = record["latest"];
|
|
9890
|
+
if (typeof checkedAt !== "number" || typeof latest !== "string" || !VERSION.test(latest)) {
|
|
9891
|
+
return null;
|
|
9892
|
+
}
|
|
9893
|
+
return { checkedAt, latest };
|
|
9894
|
+
} catch {
|
|
9895
|
+
return null;
|
|
9896
|
+
}
|
|
9897
|
+
}
|
|
9898
|
+
function writeCache(path, entry) {
|
|
9899
|
+
try {
|
|
9900
|
+
writeFileSync(path, JSON.stringify(entry), { encoding: "utf8", mode: 384 });
|
|
9901
|
+
} catch {}
|
|
9902
|
+
}
|
|
9903
|
+
function parts(version) {
|
|
9904
|
+
const withoutBuild = version.split("+")[0] ?? version;
|
|
9905
|
+
const dash = withoutBuild.indexOf("-");
|
|
9906
|
+
const numeric = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
|
|
9907
|
+
return {
|
|
9908
|
+
numbers: numeric.split(".").map((piece) => Number.parseInt(piece, 10) || 0),
|
|
9909
|
+
prerelease: dash !== -1
|
|
9910
|
+
};
|
|
9911
|
+
}
|
|
9912
|
+
function isNewer(candidate, current) {
|
|
9913
|
+
const left = parts(candidate);
|
|
9914
|
+
const right = parts(current);
|
|
9915
|
+
for (let index = 0;index < 3; index += 1) {
|
|
9916
|
+
const a = left.numbers[index] ?? 0;
|
|
9917
|
+
const b = right.numbers[index] ?? 0;
|
|
9918
|
+
if (a !== b) {
|
|
9919
|
+
return a > b;
|
|
9920
|
+
}
|
|
9921
|
+
}
|
|
9922
|
+
return !left.prerelease && right.prerelease;
|
|
9923
|
+
}
|
|
9924
|
+
async function fetchLatest() {
|
|
9925
|
+
const response = await fetch(REGISTRY, {
|
|
9926
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
9927
|
+
redirect: "error",
|
|
9928
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
9929
|
+
});
|
|
9930
|
+
if (!response.ok || response.body === null) {
|
|
9931
|
+
return null;
|
|
9932
|
+
}
|
|
9933
|
+
const declared = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
9934
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
9935
|
+
return null;
|
|
9936
|
+
}
|
|
9937
|
+
const chunks = [];
|
|
9938
|
+
let size = 0;
|
|
9939
|
+
for await (const chunk of response.body) {
|
|
9940
|
+
size += chunk.byteLength;
|
|
9941
|
+
if (size > MAX_BODY_BYTES) {
|
|
9942
|
+
return null;
|
|
9943
|
+
}
|
|
9944
|
+
chunks.push(chunk);
|
|
9945
|
+
}
|
|
9946
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
9947
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
9948
|
+
return null;
|
|
9949
|
+
}
|
|
9950
|
+
const version = parsed["version"];
|
|
9951
|
+
return typeof version === "string" && VERSION.test(version) ? version : null;
|
|
9952
|
+
}
|
|
9953
|
+
|
|
9954
|
+
class UpdateCheck {
|
|
9955
|
+
latest = null;
|
|
9956
|
+
checking = false;
|
|
9957
|
+
checkedAt = 0;
|
|
9958
|
+
retryAt = 0;
|
|
9959
|
+
backoffMs = FIRST_RETRY_MS;
|
|
9960
|
+
enabled;
|
|
9961
|
+
current;
|
|
9962
|
+
cachePath;
|
|
9963
|
+
constructor(current, environment = process.env) {
|
|
9964
|
+
this.current = current;
|
|
9965
|
+
this.enabled = (environment["GDHARNESS_NO_UPDATE_CHECK"] ?? "") === "";
|
|
9966
|
+
this.cachePath = cacheFile(environment);
|
|
9967
|
+
const cached = this.enabled ? readCache(this.cachePath) : null;
|
|
9968
|
+
if (cached !== null) {
|
|
9969
|
+
this.latest = cached.latest;
|
|
9970
|
+
this.checkedAt = cached.checkedAt;
|
|
9971
|
+
}
|
|
9972
|
+
}
|
|
9973
|
+
refresh(now = Date.now()) {
|
|
9974
|
+
if (!this.enabled || this.checking || now < this.retryAt || now - this.checkedAt < CACHE_MS) {
|
|
9975
|
+
return;
|
|
9976
|
+
}
|
|
9977
|
+
this.checking = true;
|
|
9978
|
+
fetchLatest().then((version) => {
|
|
9979
|
+
if (version === null) {
|
|
9980
|
+
this.scheduleRetry(now);
|
|
9981
|
+
return;
|
|
9982
|
+
}
|
|
9983
|
+
this.latest = version;
|
|
9984
|
+
this.checkedAt = Date.now();
|
|
9985
|
+
this.backoffMs = FIRST_RETRY_MS;
|
|
9986
|
+
this.retryAt = 0;
|
|
9987
|
+
writeCache(this.cachePath, { checkedAt: this.checkedAt, latest: version });
|
|
9988
|
+
}).catch(() => {
|
|
9989
|
+
this.scheduleRetry(now);
|
|
9990
|
+
}).finally(() => {
|
|
9991
|
+
this.checking = false;
|
|
9992
|
+
});
|
|
9993
|
+
}
|
|
9994
|
+
scheduleRetry(now) {
|
|
9995
|
+
this.retryAt = now + this.backoffMs;
|
|
9996
|
+
this.backoffMs = Math.min(this.backoffMs * 2, MAX_RETRY_MS);
|
|
9997
|
+
}
|
|
9998
|
+
notice() {
|
|
9999
|
+
const latest = this.latest;
|
|
10000
|
+
if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
|
|
10001
|
+
return null;
|
|
10002
|
+
}
|
|
10003
|
+
return {
|
|
10004
|
+
current: this.current,
|
|
10005
|
+
latest,
|
|
10006
|
+
releaseNotes: `${RELEASES}/v${latest}`,
|
|
10007
|
+
upgrade: runLine(currentRunner(), latest, "upgrade")
|
|
10008
|
+
};
|
|
10009
|
+
}
|
|
10010
|
+
}
|
|
10011
|
+
|
|
10012
|
+
// src/server-version.ts
|
|
9830
10013
|
var DEBUG_MODE = process.env["DEBUG"] === "true";
|
|
9831
10014
|
var GODOT_DEBUG_MODE_DEFAULT = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE;
|
|
9832
10015
|
var SERVER_VERSION = (() => {
|
|
9833
10016
|
try {
|
|
9834
|
-
const pkg = JSON.parse(
|
|
10017
|
+
const pkg = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
|
|
9835
10018
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
9836
10019
|
} catch {
|
|
9837
10020
|
return "0.0.0";
|
|
9838
10021
|
}
|
|
9839
10022
|
})();
|
|
10023
|
+
var UNVERSIONED = "addon from before versions were reported";
|
|
10024
|
+
function addonMismatch(addonVersion, serverVersion) {
|
|
10025
|
+
if (addonVersion === serverVersion) {
|
|
10026
|
+
return;
|
|
10027
|
+
}
|
|
10028
|
+
const reported = addonVersion ?? "";
|
|
10029
|
+
const editor = reported === "" ? UNVERSIONED : reported;
|
|
10030
|
+
const both = `The editor is running the ${editor} addon while this server ships ${serverVersion}.`;
|
|
10031
|
+
if (reported !== "" && isNewer(reported, serverVersion)) {
|
|
10032
|
+
return `${both} This server is the older half: reconnect it in your harness so it spawns ${reported}.`;
|
|
10033
|
+
}
|
|
10034
|
+
return `${both} Restart it with editor_launch restart to pick the new one up.`;
|
|
10035
|
+
}
|
|
9840
10036
|
|
|
9841
10037
|
// src/issues.ts
|
|
9842
10038
|
var NEW_ISSUE = "https://github.com/Aureliolo/gdharness/issues/new";
|
|
@@ -24500,8 +24696,8 @@ class StdioServerTransport {
|
|
|
24500
24696
|
}
|
|
24501
24697
|
|
|
24502
24698
|
// src/class-cache.ts
|
|
24503
|
-
import { existsSync, readdirSync, readFileSync as
|
|
24504
|
-
import { join } from "node:path";
|
|
24699
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
24700
|
+
import { join as join2 } from "node:path";
|
|
24505
24701
|
function declaredClasses(projectPath) {
|
|
24506
24702
|
const declared = new Map;
|
|
24507
24703
|
const visit = (directory, prefix) => {
|
|
@@ -24509,11 +24705,11 @@ function declaredClasses(projectPath) {
|
|
|
24509
24705
|
if (entry.name.startsWith(".")) {
|
|
24510
24706
|
continue;
|
|
24511
24707
|
}
|
|
24512
|
-
const path =
|
|
24708
|
+
const path = join2(directory, entry.name);
|
|
24513
24709
|
if (entry.isDirectory()) {
|
|
24514
24710
|
visit(path, `${prefix}${entry.name}/`);
|
|
24515
24711
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
24516
|
-
const found = /^class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m.exec(
|
|
24712
|
+
const found = /^class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m.exec(readFileSync3(path, "utf8"));
|
|
24517
24713
|
if (found?.[1]) {
|
|
24518
24714
|
declared.set(found[1], `res://${prefix}${entry.name}`);
|
|
24519
24715
|
}
|
|
@@ -24524,12 +24720,12 @@ function declaredClasses(projectPath) {
|
|
|
24524
24720
|
return declared;
|
|
24525
24721
|
}
|
|
24526
24722
|
function cachedClasses(projectPath) {
|
|
24527
|
-
const cache =
|
|
24528
|
-
if (!
|
|
24723
|
+
const cache = join2(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
24724
|
+
if (!existsSync2(cache)) {
|
|
24529
24725
|
return null;
|
|
24530
24726
|
}
|
|
24531
24727
|
const listed = new Map;
|
|
24532
|
-
const text =
|
|
24728
|
+
const text = readFileSync3(cache, "utf8");
|
|
24533
24729
|
for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
|
|
24534
24730
|
listed.set(entry[1] ?? "", entry[2] ?? "");
|
|
24535
24731
|
}
|
|
@@ -25621,23 +25817,23 @@ function getDefaultBridge() {
|
|
|
25621
25817
|
|
|
25622
25818
|
// src/godot-path.ts
|
|
25623
25819
|
import { execFile } from "node:child_process";
|
|
25624
|
-
import { existsSync as
|
|
25820
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
25625
25821
|
import { normalize } from "node:path";
|
|
25626
25822
|
import { promisify } from "node:util";
|
|
25627
25823
|
|
|
25628
25824
|
// src/detection.ts
|
|
25629
|
-
import { existsSync as
|
|
25630
|
-
import { homedir } from "node:os";
|
|
25631
|
-
import { join as
|
|
25825
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, statSync } from "node:fs";
|
|
25826
|
+
import { homedir as homedir2 } from "node:os";
|
|
25827
|
+
import { join as join3 } from "node:path";
|
|
25632
25828
|
function resolveHomeDirectory() {
|
|
25633
25829
|
try {
|
|
25634
|
-
return
|
|
25830
|
+
return homedir2();
|
|
25635
25831
|
} catch {
|
|
25636
25832
|
return "";
|
|
25637
25833
|
}
|
|
25638
25834
|
}
|
|
25639
25835
|
function scanDirectoryForGodotBinaries(directory, platform) {
|
|
25640
|
-
if (!directory || !
|
|
25836
|
+
if (!directory || !existsSync3(directory)) {
|
|
25641
25837
|
return [];
|
|
25642
25838
|
}
|
|
25643
25839
|
let entries;
|
|
@@ -25652,7 +25848,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
|
|
|
25652
25848
|
if (!pattern.test(name)) {
|
|
25653
25849
|
continue;
|
|
25654
25850
|
}
|
|
25655
|
-
const fullPath =
|
|
25851
|
+
const fullPath = join3(directory, name);
|
|
25656
25852
|
try {
|
|
25657
25853
|
const stat = statSync(fullPath);
|
|
25658
25854
|
if (stat.isFile()) {
|
|
@@ -25661,7 +25857,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
|
|
|
25661
25857
|
} catch {}
|
|
25662
25858
|
}
|
|
25663
25859
|
matches.sort((a, b) => b.mtime - a.mtime);
|
|
25664
|
-
return matches.map((m) =>
|
|
25860
|
+
return matches.map((m) => join3(directory, m.name));
|
|
25665
25861
|
}
|
|
25666
25862
|
function conventionalPaths(platform, home) {
|
|
25667
25863
|
const paths = ["godot"];
|
|
@@ -25776,7 +25972,7 @@ class GodotLocator {
|
|
|
25776
25972
|
return known;
|
|
25777
25973
|
}
|
|
25778
25974
|
let ok = false;
|
|
25779
|
-
if (path === "godot" ||
|
|
25975
|
+
if (path === "godot" || existsSync4(path)) {
|
|
25780
25976
|
try {
|
|
25781
25977
|
await run(path, ["--version"]);
|
|
25782
25978
|
ok = true;
|
|
@@ -25791,9 +25987,9 @@ class GodotLocator {
|
|
|
25791
25987
|
|
|
25792
25988
|
// src/headless.ts
|
|
25793
25989
|
import { execFile as execFile2 } from "node:child_process";
|
|
25794
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
25795
|
-
import { tmpdir } from "node:os";
|
|
25796
|
-
import { join as
|
|
25990
|
+
import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
25991
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
25992
|
+
import { join as join4 } from "node:path";
|
|
25797
25993
|
import { promisify as promisify2 } from "node:util";
|
|
25798
25994
|
var run2 = promisify2(execFile2);
|
|
25799
25995
|
function snakeCased(params) {
|
|
@@ -25834,9 +26030,9 @@ function reason(stdout, stderr) {
|
|
|
25834
26030
|
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
25835
26031
|
}
|
|
25836
26032
|
async function runOperation(engine, operation, params, projectPath) {
|
|
25837
|
-
const paramsDir = mkdtempSync(
|
|
25838
|
-
const paramsFile =
|
|
25839
|
-
|
|
26033
|
+
const paramsDir = mkdtempSync(join4(tmpdir2(), "gdharness-params-"));
|
|
26034
|
+
const paramsFile = join4(paramsDir, `${operation}.json`);
|
|
26035
|
+
writeFileSync2(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
25840
26036
|
const args = [
|
|
25841
26037
|
"--headless",
|
|
25842
26038
|
"--path",
|
|
@@ -26706,8 +26902,8 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
26706
26902
|
}
|
|
26707
26903
|
|
|
26708
26904
|
// src/project-scan.ts
|
|
26709
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
26710
|
-
import { join as
|
|
26905
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
26906
|
+
import { join as join5 } from "node:path";
|
|
26711
26907
|
var SKIPPED = new Set([".git", ".godot", ".import", "node_modules"]);
|
|
26712
26908
|
var ASSET_EXTENSIONS = new Set(["png", "jpg", "jpeg", "webp", "svg", "ttf", "otf", "wav", "mp3", "ogg"]);
|
|
26713
26909
|
function projectStructure(projectPath) {
|
|
@@ -26718,7 +26914,7 @@ function projectStructure(projectPath) {
|
|
|
26718
26914
|
continue;
|
|
26719
26915
|
}
|
|
26720
26916
|
if (entry.isDirectory()) {
|
|
26721
|
-
visit(
|
|
26917
|
+
visit(join5(directory, entry.name));
|
|
26722
26918
|
} else if (entry.isFile()) {
|
|
26723
26919
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
26724
26920
|
if (extension === "tscn") {
|
|
@@ -26760,7 +26956,7 @@ function searchProject(projectPath, options) {
|
|
|
26760
26956
|
if (SKIPPED.has(entry.name)) {
|
|
26761
26957
|
continue;
|
|
26762
26958
|
}
|
|
26763
|
-
const entryPath =
|
|
26959
|
+
const entryPath = join5(directory, entry.name);
|
|
26764
26960
|
if (entry.isDirectory()) {
|
|
26765
26961
|
visit(entryPath);
|
|
26766
26962
|
continue;
|
|
@@ -26771,7 +26967,7 @@ function searchProject(projectPath, options) {
|
|
|
26771
26967
|
}
|
|
26772
26968
|
result.summary.files_searched += 1;
|
|
26773
26969
|
const matches = [];
|
|
26774
|
-
for (const [index, line] of
|
|
26970
|
+
for (const [index, line] of readFileSync4(entryPath, "utf8").split(`
|
|
26775
26971
|
`).entries()) {
|
|
26776
26972
|
if (full()) {
|
|
26777
26973
|
break;
|
|
@@ -26794,7 +26990,7 @@ function searchProject(projectPath, options) {
|
|
|
26794
26990
|
}
|
|
26795
26991
|
|
|
26796
26992
|
// src/resources.ts
|
|
26797
|
-
import { readFileSync as
|
|
26993
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
26798
26994
|
import { extname, resolve as resolve3 } from "node:path";
|
|
26799
26995
|
var STATIC_RESOURCES = [
|
|
26800
26996
|
{
|
|
@@ -26965,7 +27161,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
26965
27161
|
const parsedUri = parseGodotUri(uri);
|
|
26966
27162
|
if (parsedUri.kind === "project-info") {
|
|
26967
27163
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
26968
|
-
const rawProject =
|
|
27164
|
+
const rawProject = readFileSync5(projectFilePath, "utf-8");
|
|
26969
27165
|
const parsedProject = parseProjectGodot(rawProject);
|
|
26970
27166
|
return {
|
|
26971
27167
|
mimeType: "application/json",
|
|
@@ -26974,7 +27170,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
26974
27170
|
}
|
|
26975
27171
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
26976
27172
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
26977
|
-
const text =
|
|
27173
|
+
const text = readFileSync5(filePath, "utf-8");
|
|
26978
27174
|
return {
|
|
26979
27175
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
26980
27176
|
text
|
|
@@ -27011,10 +27207,10 @@ function setupResourceHandlers(mcp, getProjectPath) {
|
|
|
27011
27207
|
}
|
|
27012
27208
|
|
|
27013
27209
|
// src/runtime-client.ts
|
|
27014
|
-
import { existsSync as
|
|
27210
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync6, unlinkSync } from "node:fs";
|
|
27015
27211
|
import { createConnection as createConnection3 } from "node:net";
|
|
27016
|
-
import { tmpdir as
|
|
27017
|
-
import { join as
|
|
27212
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
27213
|
+
import { join as join6, resolve as resolve4 } from "node:path";
|
|
27018
27214
|
|
|
27019
27215
|
// src/tool-args.ts
|
|
27020
27216
|
function asParams(value) {
|
|
@@ -27076,7 +27272,15 @@ function runtimeDirectory(variables = process.env) {
|
|
|
27076
27272
|
return explicit;
|
|
27077
27273
|
}
|
|
27078
27274
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
27079
|
-
return
|
|
27275
|
+
return join6(perUser ?? tmpdir3(), "gdharness");
|
|
27276
|
+
}
|
|
27277
|
+
function runtimeDirectories(variables = process.env) {
|
|
27278
|
+
const candidates = [runtimeDirectory(variables)];
|
|
27279
|
+
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
27280
|
+
for (const base of fallbacks) {
|
|
27281
|
+
candidates.push(join6(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
27282
|
+
}
|
|
27283
|
+
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
27080
27284
|
}
|
|
27081
27285
|
function processAlive(pid) {
|
|
27082
27286
|
try {
|
|
@@ -27089,7 +27293,7 @@ function processAlive(pid) {
|
|
|
27089
27293
|
function parseAnnouncement(file, pid) {
|
|
27090
27294
|
let fields;
|
|
27091
27295
|
try {
|
|
27092
|
-
fields = asParams(JSON.parse(
|
|
27296
|
+
fields = asParams(JSON.parse(readFileSync6(file, "utf8")));
|
|
27093
27297
|
} catch {
|
|
27094
27298
|
return null;
|
|
27095
27299
|
}
|
|
@@ -27107,8 +27311,19 @@ function parseAnnouncement(file, pid) {
|
|
|
27107
27311
|
file
|
|
27108
27312
|
};
|
|
27109
27313
|
}
|
|
27110
|
-
function discoverRuntimes(
|
|
27111
|
-
|
|
27314
|
+
function discoverRuntimes(directories = runtimeDirectories()) {
|
|
27315
|
+
const found = new Map;
|
|
27316
|
+
for (const directory of directories) {
|
|
27317
|
+
for (const endpoint of announcedIn(directory)) {
|
|
27318
|
+
if (!found.has(endpoint.pid)) {
|
|
27319
|
+
found.set(endpoint.pid, endpoint);
|
|
27320
|
+
}
|
|
27321
|
+
}
|
|
27322
|
+
}
|
|
27323
|
+
return [...found.values()].sort((a, b) => b.pid - a.pid);
|
|
27324
|
+
}
|
|
27325
|
+
function announcedIn(directory) {
|
|
27326
|
+
if (!existsSync5(directory)) {
|
|
27112
27327
|
return [];
|
|
27113
27328
|
}
|
|
27114
27329
|
const found = [];
|
|
@@ -27117,7 +27332,7 @@ function discoverRuntimes(directory = runtimeDirectory()) {
|
|
|
27117
27332
|
if (!match) {
|
|
27118
27333
|
continue;
|
|
27119
27334
|
}
|
|
27120
|
-
const file =
|
|
27335
|
+
const file = join6(directory, entry);
|
|
27121
27336
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
27122
27337
|
const endpoint = processAlive(pid) ? parseAnnouncement(file, pid) : null;
|
|
27123
27338
|
if (endpoint) {
|
|
@@ -27128,7 +27343,7 @@ function discoverRuntimes(directory = runtimeDirectory()) {
|
|
|
27128
27343
|
} catch {}
|
|
27129
27344
|
}
|
|
27130
27345
|
}
|
|
27131
|
-
return found
|
|
27346
|
+
return found;
|
|
27132
27347
|
}
|
|
27133
27348
|
function describe2(endpoint) {
|
|
27134
27349
|
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
|
|
@@ -28153,187 +28368,6 @@ function buildToolDefinitions() {
|
|
|
28153
28368
|
});
|
|
28154
28369
|
}
|
|
28155
28370
|
|
|
28156
|
-
// src/update-check.ts
|
|
28157
|
-
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
28158
|
-
import { homedir as homedir2, tmpdir as tmpdir3 } from "node:os";
|
|
28159
|
-
import { join as join6 } from "node:path";
|
|
28160
|
-
|
|
28161
|
-
// src/runner.ts
|
|
28162
|
-
function currentRunner() {
|
|
28163
|
-
const versions = process.versions;
|
|
28164
|
-
return versions["bun"] === undefined ? "npx" : "bunx";
|
|
28165
|
-
}
|
|
28166
|
-
function runLine(runner, version, rest = "") {
|
|
28167
|
-
const flag = runner === "npx" ? "-y " : "";
|
|
28168
|
-
return `${runner} ${flag}gdharness@${version}${rest === "" ? "" : ` ${rest}`}`;
|
|
28169
|
-
}
|
|
28170
|
-
|
|
28171
|
-
// src/update-check.ts
|
|
28172
|
-
var REGISTRY = "https://registry.npmjs.org/gdharness/latest";
|
|
28173
|
-
var RELEASES = "https://github.com/Aureliolo/gdharness/releases/tag";
|
|
28174
|
-
var CACHE_MS = 4 * 60 * 60 * 1000;
|
|
28175
|
-
var REQUEST_TIMEOUT_MS = 1e4;
|
|
28176
|
-
var MAX_BODY_BYTES = 1 << 20;
|
|
28177
|
-
var FIRST_RETRY_MS = 30000;
|
|
28178
|
-
var MAX_RETRY_MS = 60 * 60 * 1000;
|
|
28179
|
-
var VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
28180
|
-
function cacheDirectory(environment) {
|
|
28181
|
-
const set = (name) => {
|
|
28182
|
-
const value = environment[name];
|
|
28183
|
-
return value !== undefined && value !== "" ? value : null;
|
|
28184
|
-
};
|
|
28185
|
-
const home = set("HOME") ?? homedir2();
|
|
28186
|
-
if (process.platform === "win32") {
|
|
28187
|
-
return join6(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
28188
|
-
}
|
|
28189
|
-
if (process.platform === "darwin") {
|
|
28190
|
-
return join6(home, "Library", "Caches", "gdharness");
|
|
28191
|
-
}
|
|
28192
|
-
return join6(set("XDG_CACHE_HOME") ?? join6(home, ".cache"), "gdharness");
|
|
28193
|
-
}
|
|
28194
|
-
function cacheFile(environment = process.env) {
|
|
28195
|
-
try {
|
|
28196
|
-
const directory = cacheDirectory(environment);
|
|
28197
|
-
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
28198
|
-
return join6(directory, "update-check.json");
|
|
28199
|
-
} catch {
|
|
28200
|
-
return join6(tmpdir3(), "gdharness-update-check.json");
|
|
28201
|
-
}
|
|
28202
|
-
}
|
|
28203
|
-
function readCache(path) {
|
|
28204
|
-
try {
|
|
28205
|
-
if (!existsSync5(path)) {
|
|
28206
|
-
return null;
|
|
28207
|
-
}
|
|
28208
|
-
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
28209
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
28210
|
-
return null;
|
|
28211
|
-
}
|
|
28212
|
-
const record = parsed;
|
|
28213
|
-
const checkedAt = record["checkedAt"];
|
|
28214
|
-
const latest = record["latest"];
|
|
28215
|
-
if (typeof checkedAt !== "number" || typeof latest !== "string" || !VERSION.test(latest)) {
|
|
28216
|
-
return null;
|
|
28217
|
-
}
|
|
28218
|
-
return { checkedAt, latest };
|
|
28219
|
-
} catch {
|
|
28220
|
-
return null;
|
|
28221
|
-
}
|
|
28222
|
-
}
|
|
28223
|
-
function writeCache(path, entry) {
|
|
28224
|
-
try {
|
|
28225
|
-
writeFileSync2(path, JSON.stringify(entry), { encoding: "utf8", mode: 384 });
|
|
28226
|
-
} catch {}
|
|
28227
|
-
}
|
|
28228
|
-
function parts(version) {
|
|
28229
|
-
const withoutBuild = version.split("+")[0] ?? version;
|
|
28230
|
-
const dash = withoutBuild.indexOf("-");
|
|
28231
|
-
const numeric = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
|
|
28232
|
-
return {
|
|
28233
|
-
numbers: numeric.split(".").map((piece) => Number.parseInt(piece, 10) || 0),
|
|
28234
|
-
prerelease: dash !== -1
|
|
28235
|
-
};
|
|
28236
|
-
}
|
|
28237
|
-
function isNewer(candidate, current) {
|
|
28238
|
-
const left = parts(candidate);
|
|
28239
|
-
const right = parts(current);
|
|
28240
|
-
for (let index = 0;index < 3; index += 1) {
|
|
28241
|
-
const a = left.numbers[index] ?? 0;
|
|
28242
|
-
const b = right.numbers[index] ?? 0;
|
|
28243
|
-
if (a !== b) {
|
|
28244
|
-
return a > b;
|
|
28245
|
-
}
|
|
28246
|
-
}
|
|
28247
|
-
return !left.prerelease && right.prerelease;
|
|
28248
|
-
}
|
|
28249
|
-
async function fetchLatest() {
|
|
28250
|
-
const response = await fetch(REGISTRY, {
|
|
28251
|
-
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
28252
|
-
redirect: "error",
|
|
28253
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
28254
|
-
});
|
|
28255
|
-
if (!response.ok || response.body === null) {
|
|
28256
|
-
return null;
|
|
28257
|
-
}
|
|
28258
|
-
const declared = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
28259
|
-
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
28260
|
-
return null;
|
|
28261
|
-
}
|
|
28262
|
-
const chunks = [];
|
|
28263
|
-
let size = 0;
|
|
28264
|
-
for await (const chunk of response.body) {
|
|
28265
|
-
size += chunk.byteLength;
|
|
28266
|
-
if (size > MAX_BODY_BYTES) {
|
|
28267
|
-
return null;
|
|
28268
|
-
}
|
|
28269
|
-
chunks.push(chunk);
|
|
28270
|
-
}
|
|
28271
|
-
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
28272
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
28273
|
-
return null;
|
|
28274
|
-
}
|
|
28275
|
-
const version = parsed["version"];
|
|
28276
|
-
return typeof version === "string" && VERSION.test(version) ? version : null;
|
|
28277
|
-
}
|
|
28278
|
-
|
|
28279
|
-
class UpdateCheck {
|
|
28280
|
-
latest = null;
|
|
28281
|
-
checking = false;
|
|
28282
|
-
checkedAt = 0;
|
|
28283
|
-
retryAt = 0;
|
|
28284
|
-
backoffMs = FIRST_RETRY_MS;
|
|
28285
|
-
enabled;
|
|
28286
|
-
current;
|
|
28287
|
-
cachePath;
|
|
28288
|
-
constructor(current, environment = process.env) {
|
|
28289
|
-
this.current = current;
|
|
28290
|
-
this.enabled = (environment["GDHARNESS_NO_UPDATE_CHECK"] ?? "") === "";
|
|
28291
|
-
this.cachePath = cacheFile(environment);
|
|
28292
|
-
const cached = this.enabled ? readCache(this.cachePath) : null;
|
|
28293
|
-
if (cached !== null) {
|
|
28294
|
-
this.latest = cached.latest;
|
|
28295
|
-
this.checkedAt = cached.checkedAt;
|
|
28296
|
-
}
|
|
28297
|
-
}
|
|
28298
|
-
refresh(now = Date.now()) {
|
|
28299
|
-
if (!this.enabled || this.checking || now < this.retryAt || now - this.checkedAt < CACHE_MS) {
|
|
28300
|
-
return;
|
|
28301
|
-
}
|
|
28302
|
-
this.checking = true;
|
|
28303
|
-
fetchLatest().then((version) => {
|
|
28304
|
-
if (version === null) {
|
|
28305
|
-
this.scheduleRetry(now);
|
|
28306
|
-
return;
|
|
28307
|
-
}
|
|
28308
|
-
this.latest = version;
|
|
28309
|
-
this.checkedAt = Date.now();
|
|
28310
|
-
this.backoffMs = FIRST_RETRY_MS;
|
|
28311
|
-
this.retryAt = 0;
|
|
28312
|
-
writeCache(this.cachePath, { checkedAt: this.checkedAt, latest: version });
|
|
28313
|
-
}).catch(() => {
|
|
28314
|
-
this.scheduleRetry(now);
|
|
28315
|
-
}).finally(() => {
|
|
28316
|
-
this.checking = false;
|
|
28317
|
-
});
|
|
28318
|
-
}
|
|
28319
|
-
scheduleRetry(now) {
|
|
28320
|
-
this.retryAt = now + this.backoffMs;
|
|
28321
|
-
this.backoffMs = Math.min(this.backoffMs * 2, MAX_RETRY_MS);
|
|
28322
|
-
}
|
|
28323
|
-
notice() {
|
|
28324
|
-
const latest = this.latest;
|
|
28325
|
-
if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
|
|
28326
|
-
return null;
|
|
28327
|
-
}
|
|
28328
|
-
return {
|
|
28329
|
-
current: this.current,
|
|
28330
|
-
latest,
|
|
28331
|
-
releaseNotes: `${RELEASES}/v${latest}`,
|
|
28332
|
-
upgrade: runLine(currentRunner(), latest, "upgrade")
|
|
28333
|
-
};
|
|
28334
|
-
}
|
|
28335
|
-
}
|
|
28336
|
-
|
|
28337
28371
|
// src/server.ts
|
|
28338
28372
|
var UPDATE_NOTICE_EVERY = 500;
|
|
28339
28373
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
@@ -29325,7 +29359,7 @@ class GodotServer {
|
|
|
29325
29359
|
addonIsStale: status.connected ? stale : undefined,
|
|
29326
29360
|
bridgeAvailable: this.bridgeStartupError === null,
|
|
29327
29361
|
startupError: this.bridgeStartupError,
|
|
29328
|
-
staleNote: stale ?
|
|
29362
|
+
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
29329
29363
|
note: isPortConflict ? "Bridge port is already in use. Another gdharness instance may own the editor bridge, so this server cannot report that editor connection." : undefined,
|
|
29330
29364
|
suggestion: isPortConflict ? "Stop duplicate gdharness/MCP server instances or re-run the command from the same server process that owns the bridge port." : undefined
|
|
29331
29365
|
};
|
|
@@ -29403,6 +29437,7 @@ class GodotServer {
|
|
|
29403
29437
|
addonVersion: now.addonVersion,
|
|
29404
29438
|
serverVersion: SERVER_VERSION,
|
|
29405
29439
|
addonIsStale: now.addonVersion !== SERVER_VERSION,
|
|
29440
|
+
staleNote: addonMismatch(now.addonVersion, SERVER_VERSION),
|
|
29406
29441
|
tookMs: Date.now() - began
|
|
29407
29442
|
});
|
|
29408
29443
|
}
|
|
@@ -29436,7 +29471,8 @@ class GodotServer {
|
|
|
29436
29471
|
this.logDebug(`Launching Godot editor for project: ${project.value.path}`);
|
|
29437
29472
|
const editor = spawn(engine.value, editorArguments(project.value.path), {
|
|
29438
29473
|
stdio: "ignore",
|
|
29439
|
-
detached: true
|
|
29474
|
+
detached: true,
|
|
29475
|
+
env: { ...process.env, GDHARNESS_RUNTIME_DIR: runtimeDirectory() }
|
|
29440
29476
|
});
|
|
29441
29477
|
const started = await new Promise((resolve) => {
|
|
29442
29478
|
editor.once("spawn", () => {
|