gdharness 1.1.23 → 1.1.24
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 +325 -144
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +23 -0
- package/build/index.js +162 -19
- package/build/keeper.js +151 -50
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -7684,6 +7684,73 @@ var require_public_api = __commonJS(function(exports) {
|
|
|
7684
7684
|
exports.stringify = stringify;
|
|
7685
7685
|
});
|
|
7686
7686
|
|
|
7687
|
+
// src/paths.ts
|
|
7688
|
+
import { realpathSync } from "node:fs";
|
|
7689
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
7690
|
+
function realPathOr(path) {
|
|
7691
|
+
try {
|
|
7692
|
+
return realpathSync.native(path);
|
|
7693
|
+
} catch {
|
|
7694
|
+
return path;
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
function isSameDirectory(onePath, otherPath) {
|
|
7698
|
+
return relative(realPathOr(resolve(onePath)), realPathOr(resolve(otherPath))) === "";
|
|
7699
|
+
}
|
|
7700
|
+
function isWithinRoot(rootPath, candidatePath) {
|
|
7701
|
+
const step = relative(resolve(rootPath), resolve(candidatePath));
|
|
7702
|
+
return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
|
|
7703
|
+
}
|
|
7704
|
+
function resolveWithinProject(projectPath, candidatePath) {
|
|
7705
|
+
const trimmed = candidatePath.trim();
|
|
7706
|
+
if (trimmed === "") {
|
|
7707
|
+
return { ok: false, reason: "Path is empty." };
|
|
7708
|
+
}
|
|
7709
|
+
if (trimmed.includes("\x00")) {
|
|
7710
|
+
return { ok: false, reason: "Path contains a null byte." };
|
|
7711
|
+
}
|
|
7712
|
+
const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : trimmed;
|
|
7713
|
+
if (withoutScheme.startsWith("uid://")) {
|
|
7714
|
+
return {
|
|
7715
|
+
ok: false,
|
|
7716
|
+
reason: `Path '${trimmed}' is a UID, and a file is judged here by where it is on disk, which a UID does not say. Give the file's res:// path.`
|
|
7717
|
+
};
|
|
7718
|
+
}
|
|
7719
|
+
const scheme = OTHER_SCHEME.exec(withoutScheme);
|
|
7720
|
+
if (scheme) {
|
|
7721
|
+
return {
|
|
7722
|
+
ok: false,
|
|
7723
|
+
reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.`
|
|
7724
|
+
};
|
|
7725
|
+
}
|
|
7726
|
+
if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
|
|
7727
|
+
return {
|
|
7728
|
+
ok: false,
|
|
7729
|
+
reason: `Path '${trimmed}' is absolute; give a path inside the project, such as 'scenes/main.tscn'.`
|
|
7730
|
+
};
|
|
7731
|
+
}
|
|
7732
|
+
if (withoutScheme.includes(":")) {
|
|
7733
|
+
return {
|
|
7734
|
+
ok: false,
|
|
7735
|
+
reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
|
|
7736
|
+
};
|
|
7737
|
+
}
|
|
7738
|
+
const root = resolve(projectPath);
|
|
7739
|
+
const absolutePath = resolve(root, withoutScheme);
|
|
7740
|
+
if (!isWithinRoot(root, absolutePath)) {
|
|
7741
|
+
return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
|
|
7742
|
+
}
|
|
7743
|
+
const relativePath = relative(root, absolutePath).split(sep).join("/");
|
|
7744
|
+
if (relativePath === "") {
|
|
7745
|
+
return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
|
|
7746
|
+
}
|
|
7747
|
+
return { ok: true, absolutePath, relativePath };
|
|
7748
|
+
}
|
|
7749
|
+
var RESOURCE_SCHEME = "res://", OTHER_SCHEME;
|
|
7750
|
+
var init_paths = __esm(() => {
|
|
7751
|
+
OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
|
|
7752
|
+
});
|
|
7753
|
+
|
|
7687
7754
|
// src/runner.ts
|
|
7688
7755
|
import { existsSync as existsSync3 } from "node:fs";
|
|
7689
7756
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
@@ -7897,22 +7964,22 @@ var init_engine_run = __esm(() => {
|
|
|
7897
7964
|
|
|
7898
7965
|
// src/reimport.ts
|
|
7899
7966
|
import { createHash } from "node:crypto";
|
|
7900
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, rmSync as rmSync3, statSync as
|
|
7901
|
-
import { basename as
|
|
7967
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync3, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7968
|
+
import { basename as basename3, join as join5 } from "node:path";
|
|
7902
7969
|
function forceNextImport(projectPath, resourcePath) {
|
|
7903
7970
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
7904
7971
|
const sidecar = join5(projectPath, `${relative}.import`);
|
|
7905
7972
|
if (!existsSync5(sidecar)) {
|
|
7906
7973
|
return;
|
|
7907
7974
|
}
|
|
7908
|
-
const { atime, mtimeMs } =
|
|
7975
|
+
const { atime, mtimeMs } = statSync3(sidecar);
|
|
7909
7976
|
writeFileSync3(sidecar, readFileSync3(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
7910
7977
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
7911
7978
|
rmSync3(importRecord(projectPath, resourcePath), { force: true });
|
|
7912
7979
|
}
|
|
7913
7980
|
function importRecord(projectPath, resourcePath) {
|
|
7914
7981
|
const digest = createHash("md5").update(resourcePath).digest("hex");
|
|
7915
|
-
return join5(projectPath, ".godot", "imported", `${
|
|
7982
|
+
return join5(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
|
|
7916
7983
|
}
|
|
7917
7984
|
var init_reimport = () => {};
|
|
7918
7985
|
|
|
@@ -8038,7 +8105,7 @@ var init_scratch = __esm(() => {
|
|
|
8038
8105
|
});
|
|
8039
8106
|
|
|
8040
8107
|
// src/headless.ts
|
|
8041
|
-
import { existsSync as existsSync7, readFileSync as readFileSync4, realpathSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8108
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8042
8109
|
import { dirname as dirname4, join as join7 } from "node:path";
|
|
8043
8110
|
function snakeCased(params) {
|
|
8044
8111
|
const result = emptyRecord();
|
|
@@ -8093,7 +8160,7 @@ function scriptFault(problems, script) {
|
|
|
8093
8160
|
}
|
|
8094
8161
|
function resolvedOr(path) {
|
|
8095
8162
|
try {
|
|
8096
|
-
return
|
|
8163
|
+
return realpathSync2.native(path);
|
|
8097
8164
|
} catch {
|
|
8098
8165
|
return path;
|
|
8099
8166
|
}
|
|
@@ -8462,6 +8529,33 @@ function addonMismatch(addonVersion, serverVersion, addonDigest, shippedDigest,
|
|
|
8462
8529
|
}
|
|
8463
8530
|
return `${both} ${restart}`;
|
|
8464
8531
|
}
|
|
8532
|
+
function runtimeAddonUnknown(addon) {
|
|
8533
|
+
return addon?.version === "" && addon.digest === "";
|
|
8534
|
+
}
|
|
8535
|
+
function runtimeMismatch(pid, addon, serverVersion, shippedDigest, onDisk, projectPath) {
|
|
8536
|
+
if (runtimeAddonUnknown(addon)) {
|
|
8537
|
+
return;
|
|
8538
|
+
}
|
|
8539
|
+
if (addon !== undefined && !editorIsStale(addon.version, serverVersion, addon.digest, shippedDigest)) {
|
|
8540
|
+
return;
|
|
8541
|
+
}
|
|
8542
|
+
const game = `The game (pid ${pid})`;
|
|
8543
|
+
if (addon !== undefined && addon.version !== "" && isNewer(addon.version, serverVersion)) {
|
|
8544
|
+
return `${game} is running the ${addon.version} runtime addon while this server ships ${serverVersion}. This server is the older half: reconnect it in your harness so it spawns ${addon.version}.`;
|
|
8545
|
+
}
|
|
8546
|
+
let loaded;
|
|
8547
|
+
if (addon === undefined) {
|
|
8548
|
+
loaded = `a runtime addon from before games reported theirs, while this server ships ${serverVersion}`;
|
|
8549
|
+
} else if (addon.version === serverVersion) {
|
|
8550
|
+
loaded = `a different build of the ${serverVersion} runtime addon from the one this server ships`;
|
|
8551
|
+
} else if (addon.version === "") {
|
|
8552
|
+
loaded = `a runtime addon with no version marker, while this server ships ${serverVersion}`;
|
|
8553
|
+
} else {
|
|
8554
|
+
loaded = `the ${addon.version} runtime addon, while this server ships ${serverVersion}`;
|
|
8555
|
+
}
|
|
8556
|
+
const remedy = onDisk !== undefined && onDisk !== shippedDigest ? `The project's own runtime addon is not this server's either, so starting the game again loads the same code: gdharness upgrade in ${projectPath} installs the ${serverVersion} addons, and the game picks them up when it next starts.` : `The project holds this server's runtime addon, so the game picks it up when it next starts.`;
|
|
8557
|
+
return `${game} is running ${loaded}, and its answers come from that code. ${remedy}`;
|
|
8558
|
+
}
|
|
8465
8559
|
function markIfStale(answer, addonVersion, serverVersion, addonDigest, shippedDigest, onDisk) {
|
|
8466
8560
|
const staleNote = addonMismatch(addonVersion, serverVersion, addonDigest, shippedDigest, onDisk);
|
|
8467
8561
|
if (staleNote === undefined || typeof answer !== "object" || answer === null || Array.isArray(answer)) {
|
|
@@ -8609,75 +8703,8 @@ var init_issues = __esm(() => {
|
|
|
8609
8703
|
ENHANCEMENT_URL = `${NEW_ISSUE}?template=feature_request.md`;
|
|
8610
8704
|
});
|
|
8611
8705
|
|
|
8612
|
-
// src/paths.ts
|
|
8613
|
-
import { realpathSync as realpathSync2 } from "node:fs";
|
|
8614
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
8615
|
-
function realPathOr(path) {
|
|
8616
|
-
try {
|
|
8617
|
-
return realpathSync2.native(path);
|
|
8618
|
-
} catch {
|
|
8619
|
-
return path;
|
|
8620
|
-
}
|
|
8621
|
-
}
|
|
8622
|
-
function isSameDirectory(onePath, otherPath) {
|
|
8623
|
-
return relative(realPathOr(resolve(onePath)), realPathOr(resolve(otherPath))) === "";
|
|
8624
|
-
}
|
|
8625
|
-
function isWithinRoot(rootPath, candidatePath) {
|
|
8626
|
-
const step = relative(resolve(rootPath), resolve(candidatePath));
|
|
8627
|
-
return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
|
|
8628
|
-
}
|
|
8629
|
-
function resolveWithinProject(projectPath, candidatePath) {
|
|
8630
|
-
const trimmed = candidatePath.trim();
|
|
8631
|
-
if (trimmed === "") {
|
|
8632
|
-
return { ok: false, reason: "Path is empty." };
|
|
8633
|
-
}
|
|
8634
|
-
if (trimmed.includes("\x00")) {
|
|
8635
|
-
return { ok: false, reason: "Path contains a null byte." };
|
|
8636
|
-
}
|
|
8637
|
-
const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : trimmed;
|
|
8638
|
-
if (withoutScheme.startsWith("uid://")) {
|
|
8639
|
-
return {
|
|
8640
|
-
ok: false,
|
|
8641
|
-
reason: `Path '${trimmed}' is a UID, and a file is judged here by where it is on disk, which a UID does not say. Give the file's res:// path.`
|
|
8642
|
-
};
|
|
8643
|
-
}
|
|
8644
|
-
const scheme = OTHER_SCHEME.exec(withoutScheme);
|
|
8645
|
-
if (scheme) {
|
|
8646
|
-
return {
|
|
8647
|
-
ok: false,
|
|
8648
|
-
reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.`
|
|
8649
|
-
};
|
|
8650
|
-
}
|
|
8651
|
-
if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
|
|
8652
|
-
return {
|
|
8653
|
-
ok: false,
|
|
8654
|
-
reason: `Path '${trimmed}' is absolute; give a path inside the project, such as 'scenes/main.tscn'.`
|
|
8655
|
-
};
|
|
8656
|
-
}
|
|
8657
|
-
if (withoutScheme.includes(":")) {
|
|
8658
|
-
return {
|
|
8659
|
-
ok: false,
|
|
8660
|
-
reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
|
|
8661
|
-
};
|
|
8662
|
-
}
|
|
8663
|
-
const root = resolve(projectPath);
|
|
8664
|
-
const absolutePath = resolve(root, withoutScheme);
|
|
8665
|
-
if (!isWithinRoot(root, absolutePath)) {
|
|
8666
|
-
return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
|
|
8667
|
-
}
|
|
8668
|
-
const relativePath = relative(root, absolutePath).split(sep).join("/");
|
|
8669
|
-
if (relativePath === "") {
|
|
8670
|
-
return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
|
|
8671
|
-
}
|
|
8672
|
-
return { ok: true, absolutePath, relativePath };
|
|
8673
|
-
}
|
|
8674
|
-
var RESOURCE_SCHEME = "res://", OTHER_SCHEME;
|
|
8675
|
-
var init_paths = __esm(() => {
|
|
8676
|
-
OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
|
|
8677
|
-
});
|
|
8678
|
-
|
|
8679
8706
|
// src/class-cache.ts
|
|
8680
|
-
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as
|
|
8707
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
8681
8708
|
import { join as join9 } from "node:path";
|
|
8682
8709
|
function isToolScript(source) {
|
|
8683
8710
|
return TOOL.test(codeOf(source));
|
|
@@ -8973,7 +9000,7 @@ function uncachedClassNote(types) {
|
|
|
8973
9000
|
function cacheWrittenAt(projectPath) {
|
|
8974
9001
|
const cache = join9(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
8975
9002
|
try {
|
|
8976
|
-
return
|
|
9003
|
+
return statSync4(cache).mtimeMs;
|
|
8977
9004
|
} catch {
|
|
8978
9005
|
return null;
|
|
8979
9006
|
}
|
|
@@ -8985,7 +9012,7 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
8985
9012
|
return false;
|
|
8986
9013
|
}
|
|
8987
9014
|
try {
|
|
8988
|
-
return
|
|
9015
|
+
return statSync4(join9(projectPath, path.slice("res://".length))).mtimeMs > since;
|
|
8989
9016
|
} catch {
|
|
8990
9017
|
return false;
|
|
8991
9018
|
}
|
|
@@ -16120,13 +16147,13 @@ var init_types = __esm(() => {
|
|
|
16120
16147
|
|
|
16121
16148
|
// src/resources.ts
|
|
16122
16149
|
import { readFileSync as readFileSync8, realpathSync as realpathSync3 } from "node:fs";
|
|
16123
|
-
import { extname, resolve as
|
|
16150
|
+
import { extname, resolve as resolve3 } from "node:path";
|
|
16124
16151
|
function ensureProjectPath(getProjectPath) {
|
|
16125
16152
|
const projectPath = getProjectPath();
|
|
16126
16153
|
if (!projectPath) {
|
|
16127
16154
|
throw new Refusal("This server knows no project to read from: start it with GDHARNESS_PROJECT, connect an editor, or make any tool call naming a projectPath that holds project.godot.");
|
|
16128
16155
|
}
|
|
16129
|
-
return
|
|
16156
|
+
return resolve3(projectPath);
|
|
16130
16157
|
}
|
|
16131
16158
|
function uriPathToProjectPath(inputPath) {
|
|
16132
16159
|
const normalized = inputPath.replace(/\\/g, "/").trim();
|
|
@@ -16447,12 +16474,18 @@ import {
|
|
|
16447
16474
|
readFileSync as readFileSync9,
|
|
16448
16475
|
rmdirSync,
|
|
16449
16476
|
rmSync as rmSync5,
|
|
16450
|
-
statSync as
|
|
16477
|
+
statSync as statSync5,
|
|
16451
16478
|
writeFileSync as writeFileSync6
|
|
16452
16479
|
} from "node:fs";
|
|
16453
16480
|
import { dirname as dirname5, join as join10 } from "node:path";
|
|
16454
16481
|
import { fileURLToPath } from "node:url";
|
|
16455
16482
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
16483
|
+
return addonsDigest(EDITOR_PLUGINS, from);
|
|
16484
|
+
}
|
|
16485
|
+
function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
16486
|
+
return addonsDigest([RUNTIME_ADDON], from);
|
|
16487
|
+
}
|
|
16488
|
+
function addonsDigest(names, from) {
|
|
16456
16489
|
const hash = createHash3("sha256");
|
|
16457
16490
|
const walk = (directory, relative) => {
|
|
16458
16491
|
for (const name of readdirSync4(directory).sort()) {
|
|
@@ -16461,7 +16494,7 @@ function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
16461
16494
|
}
|
|
16462
16495
|
const path = join10(directory, name);
|
|
16463
16496
|
const inside = `${relative}/${name}`;
|
|
16464
|
-
if (
|
|
16497
|
+
if (statSync5(path).isDirectory()) {
|
|
16465
16498
|
walk(path, inside);
|
|
16466
16499
|
} else {
|
|
16467
16500
|
hash.update(`${inside}
|
|
@@ -16472,7 +16505,7 @@ function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
16472
16505
|
}
|
|
16473
16506
|
}
|
|
16474
16507
|
};
|
|
16475
|
-
for (const name of
|
|
16508
|
+
for (const name of names) {
|
|
16476
16509
|
walk(join10(from, name), name);
|
|
16477
16510
|
}
|
|
16478
16511
|
return hash.digest("hex");
|
|
@@ -16487,8 +16520,24 @@ function shippedEditorDigest() {
|
|
|
16487
16520
|
}
|
|
16488
16521
|
return shippedDigest ?? undefined;
|
|
16489
16522
|
}
|
|
16523
|
+
function shippedRuntimeDigest() {
|
|
16524
|
+
if (shippedRuntime === undefined) {
|
|
16525
|
+
try {
|
|
16526
|
+
shippedRuntime = runtimeAddonDigest();
|
|
16527
|
+
} catch {
|
|
16528
|
+
shippedRuntime = null;
|
|
16529
|
+
}
|
|
16530
|
+
}
|
|
16531
|
+
return shippedRuntime ?? undefined;
|
|
16532
|
+
}
|
|
16490
16533
|
function installedEditorDigest(projectPath) {
|
|
16491
|
-
|
|
16534
|
+
return digestMarker(projectPath, ADDONS[0]);
|
|
16535
|
+
}
|
|
16536
|
+
function installedRuntimeDigest(projectPath) {
|
|
16537
|
+
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
16538
|
+
}
|
|
16539
|
+
function digestMarker(projectPath, addon) {
|
|
16540
|
+
const marker = join10(projectPath, "addons", addon, DIGEST_MARKER);
|
|
16492
16541
|
if (!existsSync10(marker)) {
|
|
16493
16542
|
return null;
|
|
16494
16543
|
}
|
|
@@ -16505,7 +16554,7 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
|
16505
16554
|
const installed = [];
|
|
16506
16555
|
for (const name of ADDONS) {
|
|
16507
16556
|
const source = join10(from, name);
|
|
16508
|
-
if (!existsSync10(join10(source, name ===
|
|
16557
|
+
if (!existsSync10(join10(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
16509
16558
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
16510
16559
|
}
|
|
16511
16560
|
const target = join10(projectPath, "addons", name);
|
|
@@ -16518,6 +16567,8 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
|
16518
16567
|
installed.push({ name, path: target, replaced });
|
|
16519
16568
|
}
|
|
16520
16569
|
writeFileSync6(join10(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
16570
|
+
`);
|
|
16571
|
+
writeFileSync6(join10(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
|
|
16521
16572
|
`);
|
|
16522
16573
|
return installed;
|
|
16523
16574
|
}
|
|
@@ -16672,7 +16723,7 @@ function inspectProject(projectPath) {
|
|
|
16672
16723
|
problems
|
|
16673
16724
|
};
|
|
16674
16725
|
}
|
|
16675
|
-
var ADDONS, EDITOR_PLUGINS, RUNTIME_AUTOLOAD, SCRIPT_RUNS_SETTING = "gdharness/runtime/serve_script_runs", BIND_ADDRESS_SETTING = "gdharness/runtime/bind_address", PORT_SETTING = "gdharness/runtime/port", VERSION_MARKER = ".gdharness-version", DIGEST_MARKER = ".gdharness-digest", shippedDigest;
|
|
16726
|
+
var ADDONS, EDITOR_PLUGINS, RUNTIME_AUTOLOAD, SCRIPT_RUNS_SETTING = "gdharness/runtime/serve_script_runs", BIND_ADDRESS_SETTING = "gdharness/runtime/bind_address", PORT_SETTING = "gdharness/runtime/port", VERSION_MARKER = ".gdharness-version", DIGEST_MARKER = ".gdharness-digest", RUNTIME_ADDON = "gdharness_runtime", shippedDigest, shippedRuntime;
|
|
16676
16727
|
var init_setup = __esm(() => {
|
|
16677
16728
|
init_class_cache();
|
|
16678
16729
|
init_headless();
|
|
@@ -17566,7 +17617,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17566
17617
|
},
|
|
17567
17618
|
{
|
|
17568
17619
|
name: "editor_status",
|
|
17569
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs.",
|
|
17620
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs gdharness upgrade, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. Every runtime_* answer from such a game carries addonIsStale and staleNote too. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs.",
|
|
17570
17621
|
parameters: {},
|
|
17571
17622
|
requires: []
|
|
17572
17623
|
},
|
|
@@ -32796,10 +32847,10 @@ var init_stdio2 = __esm(() => {
|
|
|
32796
32847
|
});
|
|
32797
32848
|
|
|
32798
32849
|
// src/runtime-client.ts
|
|
32799
|
-
import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as
|
|
32850
|
+
import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync6, unlinkSync } from "node:fs";
|
|
32800
32851
|
import { createConnection } from "node:net";
|
|
32801
32852
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
32802
|
-
import { dirname as dirname8, join as join13, resolve as
|
|
32853
|
+
import { dirname as dirname8, join as join13, resolve as resolve4 } from "node:path";
|
|
32803
32854
|
import { setTimeout as delay } from "node:timers/promises";
|
|
32804
32855
|
function runtimeDirectory(variables = process.env) {
|
|
32805
32856
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -32815,7 +32866,7 @@ function runtimeDirectories(variables = process.env) {
|
|
|
32815
32866
|
for (const base of fallbacks) {
|
|
32816
32867
|
candidates.push(join13(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
32817
32868
|
}
|
|
32818
|
-
return [...new Set(candidates.map((path) =>
|
|
32869
|
+
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
32819
32870
|
}
|
|
32820
32871
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
32821
32872
|
for (const directory of directories) {
|
|
@@ -32868,13 +32919,14 @@ function parseAnnouncement(file, pid) {
|
|
|
32868
32919
|
};
|
|
32869
32920
|
let announcedAt;
|
|
32870
32921
|
try {
|
|
32871
|
-
announcedAt =
|
|
32922
|
+
announcedAt = statSync6(file).mtimeMs;
|
|
32872
32923
|
} catch {}
|
|
32873
32924
|
const when = announcedAt === undefined ? {} : { announcedAt };
|
|
32874
32925
|
if (protocol !== RUNTIME_PROTOCOL) {
|
|
32875
32926
|
return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named, ...when } };
|
|
32876
32927
|
}
|
|
32877
32928
|
const editorPid = readNumber(fields, "editor_pid");
|
|
32929
|
+
const addonVersion = readString(fields, "addon_version");
|
|
32878
32930
|
return {
|
|
32879
32931
|
kind: "runtime",
|
|
32880
32932
|
endpoint: {
|
|
@@ -32884,7 +32936,8 @@ function parseAnnouncement(file, pid) {
|
|
|
32884
32936
|
project: named,
|
|
32885
32937
|
file,
|
|
32886
32938
|
...editorPid !== undefined && Number.isInteger(editorPid) && editorPid > 0 ? { editorPid } : {},
|
|
32887
|
-
...when
|
|
32939
|
+
...when,
|
|
32940
|
+
...addonVersion === undefined ? {} : { addon: { version: addonVersion, digest: readString(fields, "addon_digest") ?? "" } }
|
|
32888
32941
|
}
|
|
32889
32942
|
};
|
|
32890
32943
|
}
|
|
@@ -32980,7 +33033,7 @@ function announcedIn(directory) {
|
|
|
32980
33033
|
entries.push({ file, pid, running });
|
|
32981
33034
|
if (running) {
|
|
32982
33035
|
try {
|
|
32983
|
-
candidates.push({ file, pid, writtenAt:
|
|
33036
|
+
candidates.push({ file, pid, writtenAt: statSync6(file).mtimeMs });
|
|
32984
33037
|
} catch {}
|
|
32985
33038
|
}
|
|
32986
33039
|
}
|
|
@@ -33037,7 +33090,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
|
|
|
33037
33090
|
return;
|
|
33038
33091
|
}
|
|
33039
33092
|
try {
|
|
33040
|
-
if (now -
|
|
33093
|
+
if (now - statSync6(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
|
|
33041
33094
|
unlinkSync(path);
|
|
33042
33095
|
}
|
|
33043
33096
|
} catch {}
|
|
@@ -33067,7 +33120,7 @@ function theOneProject(endpoints) {
|
|
|
33067
33120
|
if (endpoints.some((endpoint) => endpoint.project.path === "")) {
|
|
33068
33121
|
return;
|
|
33069
33122
|
}
|
|
33070
|
-
const paths = new Set(endpoints.map((endpoint) => realPathOr(
|
|
33123
|
+
const paths = new Set(endpoints.map((endpoint) => realPathOr(resolve4(endpoint.project.path))));
|
|
33071
33124
|
const only = [...paths][0];
|
|
33072
33125
|
return paths.size === 1 ? only : undefined;
|
|
33073
33126
|
}
|
|
@@ -33098,7 +33151,7 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
33098
33151
|
if (named !== undefined) {
|
|
33099
33152
|
if (projectPath !== undefined && !isSameDirectory(named.project.path, projectPath)) {
|
|
33100
33153
|
return {
|
|
33101
|
-
problem: `The game with pid ${pid} is running ${named.project.path}, not ${
|
|
33154
|
+
problem: `The game with pid ${pid} is running ${named.project.path}, not ${resolve4(projectPath)}. Running: ${endpoints.map(describe2).join("; ")}.`
|
|
33102
33155
|
};
|
|
33103
33156
|
}
|
|
33104
33157
|
return { endpoint: named };
|
|
@@ -33138,7 +33191,7 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
33138
33191
|
};
|
|
33139
33192
|
}
|
|
33140
33193
|
if (projectPath !== undefined) {
|
|
33141
|
-
const wanted =
|
|
33194
|
+
const wanted = resolve4(projectPath);
|
|
33142
33195
|
const matching = endpoints.filter((endpoint) => isSameDirectory(endpoint.project.path, wanted));
|
|
33143
33196
|
if (matching.length === 0) {
|
|
33144
33197
|
return {
|
|
@@ -33347,7 +33400,7 @@ var init_boot_note = __esm(() => {
|
|
|
33347
33400
|
|
|
33348
33401
|
// src/breakpoint-note.ts
|
|
33349
33402
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
33350
|
-
import { dirname as dirname10, isAbsolute as isAbsolute2, join as join15, relative as
|
|
33403
|
+
import { dirname as dirname10, isAbsolute as isAbsolute2, join as join15, relative as relative3 } from "node:path";
|
|
33351
33404
|
function breakpointNotePath(projectPath) {
|
|
33352
33405
|
return join15(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
33353
33406
|
}
|
|
@@ -33362,7 +33415,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
33362
33415
|
}
|
|
33363
33416
|
const note = {
|
|
33364
33417
|
breakpoints: inside.map((one) => ({
|
|
33365
|
-
script:
|
|
33418
|
+
script: relative3(root, one.scriptPath).replaceAll("\\", "/"),
|
|
33366
33419
|
lines: [...one.lines]
|
|
33367
33420
|
}))
|
|
33368
33421
|
};
|
|
@@ -33399,7 +33452,7 @@ function readBreakpointNote(projectPath) {
|
|
|
33399
33452
|
}
|
|
33400
33453
|
}
|
|
33401
33454
|
function withinProject(projectPath, scriptPath) {
|
|
33402
|
-
const rel =
|
|
33455
|
+
const rel = relative3(projectPath, scriptPath);
|
|
33403
33456
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
33404
33457
|
}
|
|
33405
33458
|
var init_breakpoint_note = __esm(() => {
|
|
@@ -34262,7 +34315,7 @@ import {
|
|
|
34262
34315
|
readFileSync as readFileSync16,
|
|
34263
34316
|
readSync,
|
|
34264
34317
|
rmSync as rmSync8,
|
|
34265
|
-
statSync as
|
|
34318
|
+
statSync as statSync7,
|
|
34266
34319
|
writeFileSync as writeFileSync11
|
|
34267
34320
|
} from "node:fs";
|
|
34268
34321
|
import { dirname as dirname12, join as join18 } from "node:path";
|
|
@@ -34325,7 +34378,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
34325
34378
|
return { log, path, ...of === undefined ? {} : { readBytes: text.length, of } };
|
|
34326
34379
|
}
|
|
34327
34380
|
function theFirstOf(path) {
|
|
34328
|
-
const whole =
|
|
34381
|
+
const whole = statSync7(path).size;
|
|
34329
34382
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
34330
34383
|
return { text: readFileSync16(path, "utf8") };
|
|
34331
34384
|
}
|
|
@@ -38230,11 +38283,53 @@ var init_junit = __esm(() => {
|
|
|
38230
38283
|
ORPHANS = /(\d+)\s+orphans/;
|
|
38231
38284
|
});
|
|
38232
38285
|
|
|
38286
|
+
// src/keeper-channel.ts
|
|
38287
|
+
import { connect as connect2, createServer as createServer2 } from "node:net";
|
|
38288
|
+
function askTheKeeperToStop(address, withinMs) {
|
|
38289
|
+
return new Promise((resolve) => {
|
|
38290
|
+
let said = "";
|
|
38291
|
+
let settled = false;
|
|
38292
|
+
const settle = (answer) => {
|
|
38293
|
+
if (!settled) {
|
|
38294
|
+
settled = true;
|
|
38295
|
+
clearTimeout(timer);
|
|
38296
|
+
socket.destroy();
|
|
38297
|
+
resolve(answer);
|
|
38298
|
+
}
|
|
38299
|
+
};
|
|
38300
|
+
const socket = connect2(address);
|
|
38301
|
+
const timer = setTimeout(() => {
|
|
38302
|
+
settle({ unreached: `it did not answer within ${withinMs} ms` });
|
|
38303
|
+
}, withinMs);
|
|
38304
|
+
socket.setEncoding("utf8");
|
|
38305
|
+
socket.on("connect", () => {
|
|
38306
|
+
socket.write(`stop
|
|
38307
|
+
`);
|
|
38308
|
+
});
|
|
38309
|
+
socket.on("data", (chunk) => {
|
|
38310
|
+
said += chunk;
|
|
38311
|
+
const line = said.split(`
|
|
38312
|
+
`)[0] ?? "";
|
|
38313
|
+
if (said.includes(`
|
|
38314
|
+
`)) {
|
|
38315
|
+
settle(line === "signalled" || line === "gone" ? line : { unreached: `it answered ${JSON.stringify(line)}` });
|
|
38316
|
+
}
|
|
38317
|
+
});
|
|
38318
|
+
socket.on("error", (error) => {
|
|
38319
|
+
settle({ unreached: error.code ?? error.message });
|
|
38320
|
+
});
|
|
38321
|
+
socket.on("close", () => {
|
|
38322
|
+
settle({ unreached: "it closed the connection without answering" });
|
|
38323
|
+
});
|
|
38324
|
+
});
|
|
38325
|
+
}
|
|
38326
|
+
var init_keeper_channel = () => {};
|
|
38327
|
+
|
|
38233
38328
|
// src/lsp_client.ts
|
|
38234
38329
|
import { existsSync as existsSync15, realpathSync as realpathSync4 } from "node:fs";
|
|
38235
38330
|
import { readFile, realpath } from "node:fs/promises";
|
|
38236
38331
|
import { createConnection as createConnection3 } from "node:net";
|
|
38237
|
-
import { dirname as dirname13, join as join19, resolve as
|
|
38332
|
+
import { dirname as dirname13, join as join19, resolve as resolve5 } from "node:path";
|
|
38238
38333
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
38239
38334
|
function diagnosticsKey(uri) {
|
|
38240
38335
|
let decoded;
|
|
@@ -38364,7 +38459,7 @@ class GodotLSPClient {
|
|
|
38364
38459
|
if (this.initialized) {
|
|
38365
38460
|
return;
|
|
38366
38461
|
}
|
|
38367
|
-
const rootPath = this.rootPath ?? dirname13(
|
|
38462
|
+
const rootPath = this.rootPath ?? dirname13(resolve5(filePath));
|
|
38368
38463
|
await this.initialize(rootPath);
|
|
38369
38464
|
}
|
|
38370
38465
|
async sendRequest(method, params) {
|
|
@@ -38506,7 +38601,7 @@ class GodotLSPClient {
|
|
|
38506
38601
|
});
|
|
38507
38602
|
}
|
|
38508
38603
|
toFileUri(filePath) {
|
|
38509
|
-
return pathToFileURL(
|
|
38604
|
+
return pathToFileURL(resolve5(filePath)).href;
|
|
38510
38605
|
}
|
|
38511
38606
|
syncDocument(filePath, content) {
|
|
38512
38607
|
const uri = this.toFileUri(filePath);
|
|
@@ -38550,7 +38645,7 @@ class GodotLSPClient {
|
|
|
38550
38645
|
}
|
|
38551
38646
|
async initialize(rootPath) {
|
|
38552
38647
|
await this.ensureConnected();
|
|
38553
|
-
const resolvedRootPath =
|
|
38648
|
+
const resolvedRootPath = resolve5(rootPath);
|
|
38554
38649
|
const rootUri = pathToFileURL(resolvedRootPath).href;
|
|
38555
38650
|
this.forgetServedWorkspace();
|
|
38556
38651
|
const result = await this.sendRequest("initialize", {
|
|
@@ -38719,7 +38814,7 @@ function normalizeLSPError(error, port) {
|
|
|
38719
38814
|
return String(error);
|
|
38720
38815
|
}
|
|
38721
38816
|
async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
38722
|
-
const requestedProjectPath =
|
|
38817
|
+
const requestedProjectPath = resolve5(projectPathValue);
|
|
38723
38818
|
let projectPath;
|
|
38724
38819
|
try {
|
|
38725
38820
|
projectPath = await realpath(requestedProjectPath);
|
|
@@ -39139,10 +39234,10 @@ import {
|
|
|
39139
39234
|
readFileSync as readFileSync19,
|
|
39140
39235
|
realpathSync as realpathSync5,
|
|
39141
39236
|
rmSync as rmSync10,
|
|
39142
|
-
statSync as
|
|
39237
|
+
statSync as statSync8,
|
|
39143
39238
|
writeFileSync as writeFileSync13
|
|
39144
39239
|
} from "node:fs";
|
|
39145
|
-
import { basename as
|
|
39240
|
+
import { basename as basename5, dirname as dirname15, join as join22, resolve as resolve6 } from "node:path";
|
|
39146
39241
|
import { promisify as promisify4 } from "node:util";
|
|
39147
39242
|
function runsDirectory() {
|
|
39148
39243
|
return join22(runtimeDirectory(), "runs");
|
|
@@ -39158,13 +39253,13 @@ function realAsFarAsItExists(path) {
|
|
|
39158
39253
|
if (parent === at) {
|
|
39159
39254
|
return path;
|
|
39160
39255
|
}
|
|
39161
|
-
missing.unshift(
|
|
39256
|
+
missing.unshift(basename5(at));
|
|
39162
39257
|
at = parent;
|
|
39163
39258
|
}
|
|
39164
39259
|
}
|
|
39165
39260
|
}
|
|
39166
39261
|
function projectKey(projectPath) {
|
|
39167
|
-
const real = realAsFarAsItExists(
|
|
39262
|
+
const real = realAsFarAsItExists(resolve6(projectPath)).replace(/[\\/]+$/, "");
|
|
39168
39263
|
const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
|
|
39169
39264
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
39170
39265
|
}
|
|
@@ -39274,6 +39369,7 @@ function recordAt(path) {
|
|
|
39274
39369
|
...typeof fields["startedBy"] === "number" ? { startedBy: fields["startedBy"] } : {},
|
|
39275
39370
|
projectPath: typeof fields["projectPath"] === "string" ? fields["projectPath"] : "",
|
|
39276
39371
|
...typeof fields["servedBy"] === "string" && fields["servedBy"] !== "" ? { servedBy: fields["servedBy"] } : {},
|
|
39372
|
+
...typeof fields["keeper"] === "string" && fields["keeper"] !== "" ? { keeper: fields["keeper"] } : {},
|
|
39277
39373
|
arguments: Array.isArray(args) ? args.filter((value) => typeof value === "string") : [],
|
|
39278
39374
|
...typeof fields["command"] === "string" ? { command: fields["command"] } : {},
|
|
39279
39375
|
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
|
|
@@ -39295,11 +39391,8 @@ async function runningAs(pid) {
|
|
|
39295
39391
|
}
|
|
39296
39392
|
}
|
|
39297
39393
|
const image = await windowsImage(pid);
|
|
39298
|
-
|
|
39299
|
-
|
|
39300
|
-
text: image,
|
|
39301
|
-
unread: "unread" in read ? read.unread : "Windows gave an empty command line"
|
|
39302
|
-
};
|
|
39394
|
+
const unread = "unread" in read ? read.unread : "Windows gave an empty command line";
|
|
39395
|
+
return "image" in image ? { kind: "image", text: image.image, unread } : { kind: "none", text: "", unread: `${unread}; tasklist ${image.why}` };
|
|
39303
39396
|
}
|
|
39304
39397
|
try {
|
|
39305
39398
|
const began = await startedAt(pid, null);
|
|
@@ -39384,9 +39477,11 @@ async function windowsImage(pid) {
|
|
|
39384
39477
|
timeout: 15000,
|
|
39385
39478
|
windowsHide: true
|
|
39386
39479
|
});
|
|
39387
|
-
|
|
39388
|
-
|
|
39389
|
-
|
|
39480
|
+
const image = /^"([^"]+)"/.exec(csv.trim())?.[1];
|
|
39481
|
+
return image === undefined ? { why: "named no process under that number" } : { image };
|
|
39482
|
+
} catch (error) {
|
|
39483
|
+
const failed = error;
|
|
39484
|
+
return { why: failed.killed === true ? "did not answer within 15000 ms" : errorMessage(error) };
|
|
39390
39485
|
}
|
|
39391
39486
|
}
|
|
39392
39487
|
async function whyNotStillTheRecordedRun(record) {
|
|
@@ -39402,11 +39497,14 @@ function whyNotTheRun(record, running, needed) {
|
|
|
39402
39497
|
if (running === null) {
|
|
39403
39498
|
return "the operating system would not describe that process";
|
|
39404
39499
|
}
|
|
39500
|
+
if (running.kind === "none") {
|
|
39501
|
+
return `the operating system would not describe that process (${running.unread ?? "no reason given"})`;
|
|
39502
|
+
}
|
|
39405
39503
|
const after = running.startedAt === undefined ? 0 : running.startedAt - (record.startedBy ?? record.startedAt);
|
|
39406
39504
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
39407
39505
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
39408
39506
|
}
|
|
39409
|
-
const engine = record.command === undefined ? null :
|
|
39507
|
+
const engine = record.command === undefined ? null : basename5(record.command);
|
|
39410
39508
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
39411
39509
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
39412
39510
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -39457,7 +39555,7 @@ function clearRunRecord(projectPath) {
|
|
|
39457
39555
|
}
|
|
39458
39556
|
function ageMs(path, now) {
|
|
39459
39557
|
try {
|
|
39460
|
-
return now -
|
|
39558
|
+
return now - statSync8(path).mtimeMs;
|
|
39461
39559
|
} catch {
|
|
39462
39560
|
return 0;
|
|
39463
39561
|
}
|
|
@@ -39510,9 +39608,9 @@ import {
|
|
|
39510
39608
|
readFileSync as readFileSync20,
|
|
39511
39609
|
readSync as readSync2,
|
|
39512
39610
|
rmdirSync as rmdirSync2,
|
|
39513
|
-
statSync as
|
|
39611
|
+
statSync as statSync9
|
|
39514
39612
|
} from "node:fs";
|
|
39515
|
-
import { basename as
|
|
39613
|
+
import { basename as basename6, dirname as dirname16, extname as extname2, isAbsolute as isAbsolute3, join as join23, normalize as normalize2, relative as relative4 } from "node:path";
|
|
39516
39614
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
39517
39615
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
39518
39616
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -39537,7 +39635,7 @@ function captureDestinationRefusal(outputPath, projects) {
|
|
|
39537
39635
|
if (!existsSync17(directory)) {
|
|
39538
39636
|
return `outputPath's directory ${dirname16(outputPath)} does not exist; a capture creates no directories.`;
|
|
39539
39637
|
}
|
|
39540
|
-
const where = join23(directory,
|
|
39638
|
+
const where = join23(directory, basename6(outputPath));
|
|
39541
39639
|
const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
39542
39640
|
if (inside !== undefined) {
|
|
39543
39641
|
return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
|
|
@@ -39551,7 +39649,7 @@ function patienceForFrames(frames, atLeast) {
|
|
|
39551
39649
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
39552
39650
|
}
|
|
39553
39651
|
function projectSpelling(projectPath, absolutePath) {
|
|
39554
|
-
return `res://${
|
|
39652
|
+
return `res://${relative4(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
39555
39653
|
}
|
|
39556
39654
|
function whichGame(args) {
|
|
39557
39655
|
return { projectPath: args["projectPath"], pid: args["pid"] };
|
|
@@ -40495,6 +40593,10 @@ class GodotServer {
|
|
|
40495
40593
|
const project = (status.connected ? status.projectPath : undefined) ?? this.ownProject;
|
|
40496
40594
|
return project === null || project === "" ? undefined : installedEditorDigest(project);
|
|
40497
40595
|
}
|
|
40596
|
+
runtimeStaleNote(endpoint) {
|
|
40597
|
+
const project = endpoint.project.path;
|
|
40598
|
+
return runtimeMismatch(endpoint.pid, endpoint.addon, SERVER_VERSION, shippedRuntimeDigest(), project === "" ? undefined : installedRuntimeDigest(project), project);
|
|
40599
|
+
}
|
|
40498
40600
|
configDisagreement() {
|
|
40499
40601
|
return this.ownProject === null ? null : configDisagrees(this.ownProject, SERVER_VERSION);
|
|
40500
40602
|
}
|
|
@@ -41429,7 +41531,7 @@ class GodotServer {
|
|
|
41429
41531
|
const name = application["config/name"];
|
|
41430
41532
|
const mainScene = application["run/main_scene"];
|
|
41431
41533
|
const info = {
|
|
41432
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
41534
|
+
name: typeof name === "string" && name !== "" ? name : basename6(project.value.path),
|
|
41433
41535
|
path: project.value.path,
|
|
41434
41536
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
41435
41537
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -41500,7 +41602,7 @@ class GodotServer {
|
|
|
41500
41602
|
output.absolutePath
|
|
41501
41603
|
];
|
|
41502
41604
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
41503
|
-
const writtenAt = () => existsSync17(output.absolutePath) ?
|
|
41605
|
+
const writtenAt = () => existsSync17(output.absolutePath) ? statSync9(output.absolutePath).mtimeMs : null;
|
|
41504
41606
|
const before = writtenAt();
|
|
41505
41607
|
let ending;
|
|
41506
41608
|
try {
|
|
@@ -42081,12 +42183,17 @@ class GodotServer {
|
|
|
42081
42183
|
ok: false,
|
|
42082
42184
|
message: `The game is held by the editor's debugger, ${describeHalt(held)}, and answers no runtime call while it is. debug_control continue lets it go.`
|
|
42083
42185
|
};
|
|
42186
|
+
const staleNote = this.runtimeStaleNote(endpoint);
|
|
42187
|
+
const unknown = runtimeAddonUnknown(endpoint.addon);
|
|
42084
42188
|
return {
|
|
42085
42189
|
pid: endpoint.pid,
|
|
42086
42190
|
port: endpoint.port,
|
|
42087
42191
|
project: endpoint.project,
|
|
42088
42192
|
...this.ownProject === null ? {} : { ofThisProject: isSameDirectory(endpoint.project.path, this.ownProject) },
|
|
42089
42193
|
editorPid: endpoint.editorPid,
|
|
42194
|
+
addonVersion: endpoint.addon === undefined || endpoint.addon.version === "" ? null : endpoint.addon.version,
|
|
42195
|
+
...unknown ? {} : { addonIsStale: staleNote !== undefined },
|
|
42196
|
+
...staleNote === undefined ? {} : { staleNote },
|
|
42090
42197
|
reachable: reply.ok,
|
|
42091
42198
|
problem: reply.ok ? null : reply.message
|
|
42092
42199
|
};
|
|
@@ -42532,7 +42639,7 @@ class GodotServer {
|
|
|
42532
42639
|
}
|
|
42533
42640
|
let announcedAt;
|
|
42534
42641
|
try {
|
|
42535
|
-
announcedAt =
|
|
42642
|
+
announcedAt = statSync9(endpoint.file).mtimeMs;
|
|
42536
42643
|
} catch {
|
|
42537
42644
|
return;
|
|
42538
42645
|
}
|
|
@@ -42769,6 +42876,13 @@ class GodotServer {
|
|
|
42769
42876
|
if (running.pid === null) {
|
|
42770
42877
|
return "gone";
|
|
42771
42878
|
}
|
|
42879
|
+
if (running.keeper !== undefined) {
|
|
42880
|
+
const ended = await this.endThroughTheKeeper(running, running.pid, running.keeper);
|
|
42881
|
+
if (ended !== null) {
|
|
42882
|
+
clearTheRecord();
|
|
42883
|
+
return ended;
|
|
42884
|
+
}
|
|
42885
|
+
}
|
|
42772
42886
|
const startedAs = {
|
|
42773
42887
|
pid: running.pid,
|
|
42774
42888
|
transcript: running.transcript ?? "",
|
|
@@ -42786,7 +42900,7 @@ class GodotServer {
|
|
|
42786
42900
|
return "refused";
|
|
42787
42901
|
}
|
|
42788
42902
|
const signalled = await runningAs(running.pid);
|
|
42789
|
-
const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
42903
|
+
const described = signalled === null || signalled.kind === "none" ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
42790
42904
|
let landed = true;
|
|
42791
42905
|
let declined = null;
|
|
42792
42906
|
try {
|
|
@@ -42810,6 +42924,23 @@ class GodotServer {
|
|
|
42810
42924
|
running.log.record(gone ? "info" : "warning", declined !== null ? `gdharness could not signal pid ${running.pid}: the operating system refused it (${declined}). It had described that process as ${described}.` : !gone ? `gdharness signalled pid ${running.pid}, which the operating system described as ${described}, and it was still running ${STOP_WAIT_MS / 1000} seconds later.` : landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
|
|
42811
42925
|
return gone ? "gone" : "lingering";
|
|
42812
42926
|
}
|
|
42927
|
+
async endThroughTheKeeper(run, pid, keeper) {
|
|
42928
|
+
const asked = await askTheKeeperToStop(keeper, KEEPER_ANSWER_MS);
|
|
42929
|
+
if (typeof asked !== "string") {
|
|
42930
|
+
this.logDebug(`The keeper of pid ${pid} could not be asked to end it (${asked.unreached})`);
|
|
42931
|
+
return null;
|
|
42932
|
+
}
|
|
42933
|
+
const gone = await untilGone([pid]);
|
|
42934
|
+
if (gone) {
|
|
42935
|
+
await this.untilTheExitIsRecorded(run);
|
|
42936
|
+
}
|
|
42937
|
+
if (asked === "signalled" && gone) {
|
|
42938
|
+
run.exitCode = null;
|
|
42939
|
+
run.exitSignal = "SIGTERM";
|
|
42940
|
+
}
|
|
42941
|
+
run.log.record(gone ? "info" : "warning", !gone ? `gdharness had the keeper holding pid ${pid} end it, and it was still running ${STOP_WAIT_MS / 1000} seconds later.` : asked === "signalled" ? `gdharness ended pid ${pid} through the keeper holding it.` : `gdharness asked the keeper holding pid ${pid} to end it, and it had already gone.`);
|
|
42942
|
+
return gone ? "gone" : "lingering";
|
|
42943
|
+
}
|
|
42813
42944
|
spawnGame(godotPath, cmdArgs, env) {
|
|
42814
42945
|
const child = spawn4(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
42815
42946
|
const cancelled = callSignal();
|
|
@@ -42899,6 +43030,8 @@ class GodotServer {
|
|
|
42899
43030
|
}
|
|
42900
43031
|
const startedBy = Date.now();
|
|
42901
43032
|
sweepTranscripts();
|
|
43033
|
+
const noted = readRunRecord(projectPath);
|
|
43034
|
+
const keeper = noted?.pid === launched.pid ? noted.keeper : undefined;
|
|
42902
43035
|
return {
|
|
42903
43036
|
pid: launched.pid,
|
|
42904
43037
|
log: new GameLog,
|
|
@@ -42907,6 +43040,7 @@ class GodotServer {
|
|
|
42907
43040
|
projectPath,
|
|
42908
43041
|
startedAt,
|
|
42909
43042
|
startedBy,
|
|
43043
|
+
...keeper === undefined ? {} : { keeper },
|
|
42910
43044
|
command: godotPath,
|
|
42911
43045
|
exitCode: null,
|
|
42912
43046
|
exitSignal: null,
|
|
@@ -43139,6 +43273,7 @@ class GodotServer {
|
|
|
43139
43273
|
projectPath: record.projectPath === "" ? null : record.projectPath,
|
|
43140
43274
|
startedAt: record.startedAt,
|
|
43141
43275
|
...record.startedBy === undefined ? {} : { startedBy: record.startedBy },
|
|
43276
|
+
...record.keeper === undefined ? {} : { keeper: record.keeper },
|
|
43142
43277
|
...record.command === undefined ? {} : { command: record.command },
|
|
43143
43278
|
exitCode: null,
|
|
43144
43279
|
exitSignal: null,
|
|
@@ -44106,7 +44241,7 @@ class GodotServer {
|
|
|
44106
44241
|
return !run.announcedBefore.has(one.pid);
|
|
44107
44242
|
}
|
|
44108
44243
|
try {
|
|
44109
|
-
return
|
|
44244
|
+
return statSync9(one.file).mtimeMs >= run.startedAt;
|
|
44110
44245
|
} catch {
|
|
44111
44246
|
return false;
|
|
44112
44247
|
}
|
|
@@ -44182,6 +44317,11 @@ class GodotServer {
|
|
|
44182
44317
|
}
|
|
44183
44318
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
44184
44319
|
const screenshotPath = screenshotDir ? join23(screenshotDir, "capture.png") : null;
|
|
44320
|
+
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
44321
|
+
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
44322
|
+
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|
|
44323
|
+
|
|
44324
|
+
${staleNote}`);
|
|
44185
44325
|
try {
|
|
44186
44326
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
44187
44327
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
@@ -44191,22 +44331,23 @@ class GodotServer {
|
|
|
44191
44331
|
command,
|
|
44192
44332
|
waitedMs: timeoutMs,
|
|
44193
44333
|
note: reply.message,
|
|
44194
|
-
...answeredBy
|
|
44334
|
+
...answeredBy,
|
|
44335
|
+
...stale
|
|
44195
44336
|
});
|
|
44196
44337
|
}
|
|
44197
44338
|
if (!reply.ok) {
|
|
44198
|
-
return
|
|
44339
|
+
return refused(reply.message);
|
|
44199
44340
|
}
|
|
44200
44341
|
const { id: _id, ...payload } = reply.payload;
|
|
44201
44342
|
if (!expectsScreenshot) {
|
|
44202
|
-
return this.jsonTextResponse({ ...payload, ...answeredBy });
|
|
44343
|
+
return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale });
|
|
44203
44344
|
}
|
|
44204
44345
|
const returnedPath = readString(payload, "path");
|
|
44205
44346
|
if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
|
|
44206
|
-
return
|
|
44347
|
+
return refused(`The game answered a capture with ${JSON.stringify(payload)}`);
|
|
44207
44348
|
}
|
|
44208
44349
|
if (normalize2(returnedPath) !== normalize2(screenshotPath)) {
|
|
44209
|
-
return
|
|
44350
|
+
return refused(`Rejected screenshot file path outside the managed capture path: '${returnedPath}'`);
|
|
44210
44351
|
}
|
|
44211
44352
|
const dimensions = `${readNumber(payload, "width") ?? 0}x${readNumber(payload, "height") ?? 0} ${readString(payload, "format") ?? "unknown"}`;
|
|
44212
44353
|
if (saveTo !== null) {
|
|
@@ -44220,7 +44361,9 @@ class GodotServer {
|
|
|
44220
44361
|
content: [
|
|
44221
44362
|
{
|
|
44222
44363
|
type: "text",
|
|
44223
|
-
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${saveTo === null ? "" : `. Saved to ${saveTo}`}`
|
|
44364
|
+
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${saveTo === null ? "" : `. Saved to ${saveTo}`}${staleNote === undefined ? "" : `
|
|
44365
|
+
|
|
44366
|
+
${staleNote}`}`
|
|
44224
44367
|
},
|
|
44225
44368
|
{ type: "image", data: readFileSync20(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
44226
44369
|
]
|
|
@@ -44323,7 +44466,7 @@ async function runGodotServer() {
|
|
|
44323
44466
|
const server = new GodotServer;
|
|
44324
44467
|
await server.run();
|
|
44325
44468
|
}
|
|
44326
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, GREETING_WAIT_MS = 1e4, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, 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, SCAN_WAIT_MS = 30000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
44469
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, GREETING_WAIT_MS = 1e4, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, 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, SCAN_WAIT_MS = 30000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
44327
44470
|
var init_server3 = __esm(() => {
|
|
44328
44471
|
init_mcp();
|
|
44329
44472
|
init_stdio2();
|
|
@@ -44348,6 +44491,7 @@ var init_server3 = __esm(() => {
|
|
|
44348
44491
|
init_headless_operations();
|
|
44349
44492
|
init_issues();
|
|
44350
44493
|
init_junit();
|
|
44494
|
+
init_keeper_channel();
|
|
44351
44495
|
init_lsp_client();
|
|
44352
44496
|
init_outside();
|
|
44353
44497
|
init_paths();
|
|
@@ -44415,12 +44559,12 @@ init_bridge_announce();
|
|
|
44415
44559
|
init_errors();
|
|
44416
44560
|
init_godot_path();
|
|
44417
44561
|
import { existsSync as existsSync18 } from "node:fs";
|
|
44418
|
-
import { join as join24, resolve as
|
|
44562
|
+
import { join as join24, resolve as resolve7 } from "node:path";
|
|
44419
44563
|
|
|
44420
44564
|
// src/harnesses.ts
|
|
44421
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
44565
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
44422
44566
|
import { homedir as homedir2 } from "node:os";
|
|
44423
|
-
import { dirname as dirname3, join as join4 } from "node:path";
|
|
44567
|
+
import { basename as basename2, dirname as dirname3, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
|
|
44424
44568
|
import process4 from "node:process";
|
|
44425
44569
|
|
|
44426
44570
|
// node_modules/yaml/dist/index.js
|
|
@@ -44563,6 +44707,7 @@ function isEmptyCollection(node) {
|
|
|
44563
44707
|
|
|
44564
44708
|
// src/harnesses.ts
|
|
44565
44709
|
init_errors();
|
|
44710
|
+
init_paths();
|
|
44566
44711
|
init_runner();
|
|
44567
44712
|
var SERVER_KEY = "gdharness";
|
|
44568
44713
|
function launchFor(version, godotPath, projectPath, runner = currentRunner()) {
|
|
@@ -45135,6 +45280,35 @@ function recordedEnginePath(projectPath) {
|
|
|
45135
45280
|
}
|
|
45136
45281
|
return null;
|
|
45137
45282
|
}
|
|
45283
|
+
function mainCheckoutOf(projectPath) {
|
|
45284
|
+
let top = resolve2(projectPath);
|
|
45285
|
+
while (!existsSync4(join4(top, ".git"))) {
|
|
45286
|
+
const up = dirname3(top);
|
|
45287
|
+
if (up === top) {
|
|
45288
|
+
return null;
|
|
45289
|
+
}
|
|
45290
|
+
top = up;
|
|
45291
|
+
}
|
|
45292
|
+
const marker = join4(top, ".git");
|
|
45293
|
+
if (!statSync2(marker).isFile()) {
|
|
45294
|
+
return null;
|
|
45295
|
+
}
|
|
45296
|
+
const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync2(marker, "utf8"))?.[1]?.trim();
|
|
45297
|
+
if (pointer === undefined) {
|
|
45298
|
+
return null;
|
|
45299
|
+
}
|
|
45300
|
+
const entry = resolve2(top, pointer);
|
|
45301
|
+
const commonFile = join4(entry, "commondir");
|
|
45302
|
+
if (!existsSync4(commonFile)) {
|
|
45303
|
+
return null;
|
|
45304
|
+
}
|
|
45305
|
+
const common = resolve2(entry, readFileSync2(commonFile, "utf8").trim());
|
|
45306
|
+
if (basename2(common) !== ".git") {
|
|
45307
|
+
return null;
|
|
45308
|
+
}
|
|
45309
|
+
const main = join4(dirname3(common), relative2(top, resolve2(projectPath)));
|
|
45310
|
+
return isSameDirectory(main, projectPath) || !existsSync4(join4(main, "project.godot")) ? null : main;
|
|
45311
|
+
}
|
|
45138
45312
|
function entryEnvironment(harness, projectPath) {
|
|
45139
45313
|
if (harness.toml || harness.yaml || harness.snippet) {
|
|
45140
45314
|
return;
|
|
@@ -45326,7 +45500,7 @@ init_setup();
|
|
|
45326
45500
|
init_setup();
|
|
45327
45501
|
init_tool_definitions();
|
|
45328
45502
|
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
45329
|
-
import { basename as
|
|
45503
|
+
import { basename as basename4, dirname as dirname6, join as join11 } from "node:path";
|
|
45330
45504
|
|
|
45331
45505
|
// src/tool-reference.ts
|
|
45332
45506
|
init_headless_operations();
|
|
@@ -45451,7 +45625,8 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
45451
45625
|
(an older \`addonVersion\` on the same code is not stale), and \`staleNote\` says which half is behind: an editor that
|
|
45452
45626
|
needs restarting, or a server that needs reconnecting in your harness. Every answer that came
|
|
45453
45627
|
from the editor carries the same two when they differ, so a stale addon cannot answer quietly:
|
|
45454
|
-
believe a restart over a diagnostic that arrived with them.
|
|
45628
|
+
believe a restart over a diagnostic that arrived with them. A game's runtime addon is judged
|
|
45629
|
+
the same way, under each entry in \`runtimes\` and on every \`runtime_*\` answer from it.
|
|
45455
45630
|
- Start the game with \`editor_run start\`, never by spawning an engine. The editor plays it, so
|
|
45456
45631
|
its debugger holds it, which is what gives the \`debug_*\` tools something to talk to. Its answer
|
|
45457
45632
|
says under \`runtime\` whether the game can be talked to yet; when it cannot, \`mayYetAnnounce\`
|
|
@@ -45653,7 +45828,7 @@ function removeSkill(directories, projectPath) {
|
|
|
45653
45828
|
function pruneEmpty(directory, projectPath) {
|
|
45654
45829
|
const shared = join11(projectPath, ".agents");
|
|
45655
45830
|
let path = directory;
|
|
45656
|
-
while (path !== projectPath && (
|
|
45831
|
+
while (path !== projectPath && (basename4(path) === "skills" || path === shared)) {
|
|
45657
45832
|
if (readdirSync5(path).length > 0) {
|
|
45658
45833
|
return;
|
|
45659
45834
|
}
|
|
@@ -45704,7 +45879,7 @@ class UsageError extends Refusal {
|
|
|
45704
45879
|
}
|
|
45705
45880
|
function projectArgument(at) {
|
|
45706
45881
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
45707
|
-
const projectPath =
|
|
45882
|
+
const projectPath = resolve7(named ?? ".");
|
|
45708
45883
|
if (!existsSync18(join24(projectPath, "project.godot"))) {
|
|
45709
45884
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
45710
45885
|
}
|
|
@@ -45716,11 +45891,17 @@ async function engine(projectPath) {
|
|
|
45716
45891
|
return { godotPath: located.path, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT };
|
|
45717
45892
|
}
|
|
45718
45893
|
const recorded = located.named === null && projectPath !== undefined ? recordedEnginePath(projectPath) : null;
|
|
45719
|
-
if (recorded
|
|
45894
|
+
if (recorded !== null) {
|
|
45895
|
+
return { godotPath: recorded, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT };
|
|
45896
|
+
}
|
|
45897
|
+
const main = located.named === null && projectPath !== undefined ? mainCheckoutOf(projectPath) : null;
|
|
45898
|
+
const theirs = main === null ? null : recordedEnginePath(main);
|
|
45899
|
+
if (main === null || theirs === null) {
|
|
45720
45900
|
const refusal = GodotLocator.refusal(located);
|
|
45721
45901
|
throw new UsageError(`${refusal.message} ${refusal.advice.join(". ")}.`);
|
|
45722
45902
|
}
|
|
45723
|
-
|
|
45903
|
+
console.error(`engine: ${theirs}, which the harness config of ${main} names; this project is a git worktree of it`);
|
|
45904
|
+
return { godotPath: theirs, script: shippedOperationsScript(), debug: GODOT_DEBUG_MODE_DEFAULT };
|
|
45724
45905
|
}
|
|
45725
45906
|
function said2(outcome, what) {
|
|
45726
45907
|
if (!outcome.ok) {
|