gdharness 0.5.0 → 0.5.2
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 +301 -250
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +9 -1
- package/build/index.js +279 -228
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -160,6 +160,19 @@ function runArguments(options) {
|
|
|
160
160
|
function editorArguments(projectPath) {
|
|
161
161
|
return ["-e", "--path", projectPath];
|
|
162
162
|
}
|
|
163
|
+
function userDataIn(home, variables = process.env) {
|
|
164
|
+
const moved = ["APPDATA", "XDG_DATA_HOME"];
|
|
165
|
+
const carried = {};
|
|
166
|
+
for (const [name, value] of Object.entries(variables)) {
|
|
167
|
+
if (!moved.some((named) => named.toLowerCase() === name.toLowerCase())) {
|
|
168
|
+
carried[name] = value;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const named of moved) {
|
|
172
|
+
carried[named] = home;
|
|
173
|
+
}
|
|
174
|
+
return carried;
|
|
175
|
+
}
|
|
163
176
|
|
|
164
177
|
// node_modules/yaml/dist/nodes/identity.js
|
|
165
178
|
var require_identity = __commonJS(function(exports) {
|
|
@@ -7228,15 +7241,196 @@ var init_game_log = __esm(() => {
|
|
|
7228
7241
|
COLOUR_CODE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
7229
7242
|
});
|
|
7230
7243
|
|
|
7244
|
+
// src/update-check.ts
|
|
7245
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7246
|
+
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
7247
|
+
import { join as join4 } from "node:path";
|
|
7248
|
+
function cacheDirectory(environment) {
|
|
7249
|
+
const set = (name) => {
|
|
7250
|
+
const value = environment[name];
|
|
7251
|
+
return value !== undefined && value !== "" ? value : null;
|
|
7252
|
+
};
|
|
7253
|
+
const home = set("HOME") ?? homedir3();
|
|
7254
|
+
if (process.platform === "win32") {
|
|
7255
|
+
return join4(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
7256
|
+
}
|
|
7257
|
+
if (process.platform === "darwin") {
|
|
7258
|
+
return join4(home, "Library", "Caches", "gdharness");
|
|
7259
|
+
}
|
|
7260
|
+
return join4(set("XDG_CACHE_HOME") ?? join4(home, ".cache"), "gdharness");
|
|
7261
|
+
}
|
|
7262
|
+
function cacheFile(environment = process.env) {
|
|
7263
|
+
try {
|
|
7264
|
+
const directory = cacheDirectory(environment);
|
|
7265
|
+
mkdirSync2(directory, { recursive: true, mode: 448 });
|
|
7266
|
+
return join4(directory, "update-check.json");
|
|
7267
|
+
} catch {
|
|
7268
|
+
return join4(tmpdir2(), "gdharness-update-check.json");
|
|
7269
|
+
}
|
|
7270
|
+
}
|
|
7271
|
+
function readCache(path) {
|
|
7272
|
+
try {
|
|
7273
|
+
if (!existsSync4(path)) {
|
|
7274
|
+
return null;
|
|
7275
|
+
}
|
|
7276
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
7277
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
7278
|
+
return null;
|
|
7279
|
+
}
|
|
7280
|
+
const record = parsed;
|
|
7281
|
+
const checkedAt = record["checkedAt"];
|
|
7282
|
+
const latest = record["latest"];
|
|
7283
|
+
if (typeof checkedAt !== "number" || typeof latest !== "string" || !VERSION.test(latest)) {
|
|
7284
|
+
return null;
|
|
7285
|
+
}
|
|
7286
|
+
return { checkedAt, latest };
|
|
7287
|
+
} catch {
|
|
7288
|
+
return null;
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
function writeCache(path, entry) {
|
|
7292
|
+
try {
|
|
7293
|
+
writeFileSync3(path, JSON.stringify(entry), { encoding: "utf8", mode: 384 });
|
|
7294
|
+
} catch {}
|
|
7295
|
+
}
|
|
7296
|
+
function parts(version) {
|
|
7297
|
+
const withoutBuild = version.split("+")[0] ?? version;
|
|
7298
|
+
const dash = withoutBuild.indexOf("-");
|
|
7299
|
+
const numeric = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
|
|
7300
|
+
return {
|
|
7301
|
+
numbers: numeric.split(".").map((piece) => Number.parseInt(piece, 10) || 0),
|
|
7302
|
+
prerelease: dash !== -1
|
|
7303
|
+
};
|
|
7304
|
+
}
|
|
7305
|
+
function isNewer(candidate, current) {
|
|
7306
|
+
const left = parts(candidate);
|
|
7307
|
+
const right = parts(current);
|
|
7308
|
+
for (let index = 0;index < 3; index += 1) {
|
|
7309
|
+
const a = left.numbers[index] ?? 0;
|
|
7310
|
+
const b = right.numbers[index] ?? 0;
|
|
7311
|
+
if (a !== b) {
|
|
7312
|
+
return a > b;
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
return !left.prerelease && right.prerelease;
|
|
7316
|
+
}
|
|
7317
|
+
async function fetchLatest() {
|
|
7318
|
+
const response = await fetch(REGISTRY, {
|
|
7319
|
+
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
7320
|
+
redirect: "error",
|
|
7321
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
7322
|
+
});
|
|
7323
|
+
if (!response.ok || response.body === null) {
|
|
7324
|
+
return null;
|
|
7325
|
+
}
|
|
7326
|
+
const declared = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
7327
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
7328
|
+
return null;
|
|
7329
|
+
}
|
|
7330
|
+
const chunks = [];
|
|
7331
|
+
let size = 0;
|
|
7332
|
+
for await (const chunk of response.body) {
|
|
7333
|
+
size += chunk.byteLength;
|
|
7334
|
+
if (size > MAX_BODY_BYTES) {
|
|
7335
|
+
return null;
|
|
7336
|
+
}
|
|
7337
|
+
chunks.push(chunk);
|
|
7338
|
+
}
|
|
7339
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
7340
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
7341
|
+
return null;
|
|
7342
|
+
}
|
|
7343
|
+
const version = parsed["version"];
|
|
7344
|
+
return typeof version === "string" && VERSION.test(version) ? version : null;
|
|
7345
|
+
}
|
|
7346
|
+
|
|
7347
|
+
class UpdateCheck {
|
|
7348
|
+
latest = null;
|
|
7349
|
+
checking = false;
|
|
7350
|
+
checkedAt = 0;
|
|
7351
|
+
retryAt = 0;
|
|
7352
|
+
backoffMs = FIRST_RETRY_MS;
|
|
7353
|
+
enabled;
|
|
7354
|
+
current;
|
|
7355
|
+
cachePath;
|
|
7356
|
+
constructor(current, environment = process.env) {
|
|
7357
|
+
this.current = current;
|
|
7358
|
+
this.enabled = (environment["GDHARNESS_NO_UPDATE_CHECK"] ?? "") === "";
|
|
7359
|
+
this.cachePath = cacheFile(environment);
|
|
7360
|
+
const cached = this.enabled ? readCache(this.cachePath) : null;
|
|
7361
|
+
if (cached !== null) {
|
|
7362
|
+
this.latest = cached.latest;
|
|
7363
|
+
this.checkedAt = cached.checkedAt;
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
refresh(now = Date.now()) {
|
|
7367
|
+
if (!this.enabled || this.checking || now < this.retryAt || now - this.checkedAt < CACHE_MS) {
|
|
7368
|
+
return;
|
|
7369
|
+
}
|
|
7370
|
+
this.checking = true;
|
|
7371
|
+
fetchLatest().then((version) => {
|
|
7372
|
+
if (version === null) {
|
|
7373
|
+
this.scheduleRetry(now);
|
|
7374
|
+
return;
|
|
7375
|
+
}
|
|
7376
|
+
this.latest = version;
|
|
7377
|
+
this.checkedAt = Date.now();
|
|
7378
|
+
this.backoffMs = FIRST_RETRY_MS;
|
|
7379
|
+
this.retryAt = 0;
|
|
7380
|
+
writeCache(this.cachePath, { checkedAt: this.checkedAt, latest: version });
|
|
7381
|
+
}).catch(() => {
|
|
7382
|
+
this.scheduleRetry(now);
|
|
7383
|
+
}).finally(() => {
|
|
7384
|
+
this.checking = false;
|
|
7385
|
+
});
|
|
7386
|
+
}
|
|
7387
|
+
scheduleRetry(now) {
|
|
7388
|
+
this.retryAt = now + this.backoffMs;
|
|
7389
|
+
this.backoffMs = Math.min(this.backoffMs * 2, MAX_RETRY_MS);
|
|
7390
|
+
}
|
|
7391
|
+
notice() {
|
|
7392
|
+
const latest = this.latest;
|
|
7393
|
+
if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
|
|
7394
|
+
return null;
|
|
7395
|
+
}
|
|
7396
|
+
return {
|
|
7397
|
+
current: this.current,
|
|
7398
|
+
latest,
|
|
7399
|
+
releaseNotes: `${RELEASES}/v${latest}`,
|
|
7400
|
+
upgrade: runLine(currentRunner(), latest, "upgrade")
|
|
7401
|
+
};
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
var REGISTRY = "https://registry.npmjs.org/gdharness/latest", RELEASES = "https://github.com/Aureliolo/gdharness/releases/tag", CACHE_MS, REQUEST_TIMEOUT_MS = 1e4, MAX_BODY_BYTES, FIRST_RETRY_MS = 30000, MAX_RETRY_MS, VERSION;
|
|
7405
|
+
var init_update_check = __esm(() => {
|
|
7406
|
+
CACHE_MS = 4 * 60 * 60 * 1000;
|
|
7407
|
+
MAX_BODY_BYTES = 1 << 20;
|
|
7408
|
+
MAX_RETRY_MS = 60 * 60 * 1000;
|
|
7409
|
+
VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
7410
|
+
});
|
|
7411
|
+
|
|
7231
7412
|
// src/server-version.ts
|
|
7232
|
-
import { readFileSync as
|
|
7233
|
-
|
|
7413
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7414
|
+
function addonMismatch(addonVersion, serverVersion) {
|
|
7415
|
+
if (addonVersion === serverVersion) {
|
|
7416
|
+
return;
|
|
7417
|
+
}
|
|
7418
|
+
const reported = addonVersion ?? "";
|
|
7419
|
+
const editor = reported === "" ? UNVERSIONED : reported;
|
|
7420
|
+
const both = `The editor is running the ${editor} addon while this server ships ${serverVersion}.`;
|
|
7421
|
+
if (reported !== "" && isNewer(reported, serverVersion)) {
|
|
7422
|
+
return `${both} This server is the older half: reconnect it in your harness so it spawns ${reported}.`;
|
|
7423
|
+
}
|
|
7424
|
+
return `${both} Restart it with editor_launch restart to pick the new one up.`;
|
|
7425
|
+
}
|
|
7426
|
+
var DEBUG_MODE, GODOT_DEBUG_MODE_DEFAULT, SERVER_VERSION, UNVERSIONED = "addon from before versions were reported";
|
|
7234
7427
|
var init_server_version = __esm(() => {
|
|
7428
|
+
init_update_check();
|
|
7235
7429
|
DEBUG_MODE = process.env["DEBUG"] === "true";
|
|
7236
7430
|
GODOT_DEBUG_MODE_DEFAULT = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE;
|
|
7237
7431
|
SERVER_VERSION = (() => {
|
|
7238
7432
|
try {
|
|
7239
|
-
const pkg = JSON.parse(
|
|
7433
|
+
const pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
|
|
7240
7434
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
7241
7435
|
} catch {
|
|
7242
7436
|
return "0.0.0";
|
|
@@ -7245,8 +7439,8 @@ var init_server_version = __esm(() => {
|
|
|
7245
7439
|
});
|
|
7246
7440
|
|
|
7247
7441
|
// src/class-cache.ts
|
|
7248
|
-
import { existsSync as
|
|
7249
|
-
import { join as
|
|
7442
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
7443
|
+
import { join as join5 } from "node:path";
|
|
7250
7444
|
function declaredClasses(projectPath) {
|
|
7251
7445
|
const declared = new Map;
|
|
7252
7446
|
const visit = (directory, prefix) => {
|
|
@@ -7254,11 +7448,11 @@ function declaredClasses(projectPath) {
|
|
|
7254
7448
|
if (entry.name.startsWith(".")) {
|
|
7255
7449
|
continue;
|
|
7256
7450
|
}
|
|
7257
|
-
const path =
|
|
7451
|
+
const path = join5(directory, entry.name);
|
|
7258
7452
|
if (entry.isDirectory()) {
|
|
7259
7453
|
visit(path, `${prefix}${entry.name}/`);
|
|
7260
7454
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
7261
|
-
const found = /^class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m.exec(
|
|
7455
|
+
const found = /^class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m.exec(readFileSync5(path, "utf8"));
|
|
7262
7456
|
if (found?.[1]) {
|
|
7263
7457
|
declared.set(found[1], `res://${prefix}${entry.name}`);
|
|
7264
7458
|
}
|
|
@@ -7269,12 +7463,12 @@ function declaredClasses(projectPath) {
|
|
|
7269
7463
|
return declared;
|
|
7270
7464
|
}
|
|
7271
7465
|
function cachedClasses(projectPath) {
|
|
7272
|
-
const cache =
|
|
7273
|
-
if (!
|
|
7466
|
+
const cache = join5(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
7467
|
+
if (!existsSync5(cache)) {
|
|
7274
7468
|
return null;
|
|
7275
7469
|
}
|
|
7276
7470
|
const listed = new Map;
|
|
7277
|
-
const text =
|
|
7471
|
+
const text = readFileSync5(cache, "utf8");
|
|
7278
7472
|
for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
|
|
7279
7473
|
listed.set(entry[1] ?? "", entry[2] ?? "");
|
|
7280
7474
|
}
|
|
@@ -7291,9 +7485,9 @@ var init_class_cache = () => {};
|
|
|
7291
7485
|
|
|
7292
7486
|
// src/headless.ts
|
|
7293
7487
|
import { execFile as execFile3 } from "node:child_process";
|
|
7294
|
-
import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, writeFileSync as
|
|
7295
|
-
import { tmpdir as
|
|
7296
|
-
import { join as
|
|
7488
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7489
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
7490
|
+
import { join as join6 } from "node:path";
|
|
7297
7491
|
import { promisify as promisify3 } from "node:util";
|
|
7298
7492
|
function snakeCased2(params) {
|
|
7299
7493
|
const result = emptyRecord();
|
|
@@ -7333,9 +7527,9 @@ function reason2(stdout, stderr) {
|
|
|
7333
7527
|
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
7334
7528
|
}
|
|
7335
7529
|
async function runOperation2(engine, operation, params, projectPath) {
|
|
7336
|
-
const paramsDir = mkdtempSync2(
|
|
7337
|
-
const paramsFile =
|
|
7338
|
-
|
|
7530
|
+
const paramsDir = mkdtempSync2(join6(tmpdir3(), "gdharness-params-"));
|
|
7531
|
+
const paramsFile = join6(paramsDir, `${operation}.json`);
|
|
7532
|
+
writeFileSync4(paramsFile, JSON.stringify(snakeCased2(params)), "utf8");
|
|
7339
7533
|
const args = [
|
|
7340
7534
|
"--headless",
|
|
7341
7535
|
"--path",
|
|
@@ -14537,7 +14731,7 @@ var init_paths = __esm(() => {
|
|
|
14537
14731
|
});
|
|
14538
14732
|
|
|
14539
14733
|
// src/resources.ts
|
|
14540
|
-
import { readFileSync as
|
|
14734
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
14541
14735
|
import { extname, resolve as resolve2 } from "node:path";
|
|
14542
14736
|
function ensureProjectPath(getProjectPath) {
|
|
14543
14737
|
const projectPath = getProjectPath();
|
|
@@ -14679,7 +14873,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
14679
14873
|
const parsedUri = parseGodotUri(uri);
|
|
14680
14874
|
if (parsedUri.kind === "project-info") {
|
|
14681
14875
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
14682
|
-
const rawProject =
|
|
14876
|
+
const rawProject = readFileSync6(projectFilePath, "utf-8");
|
|
14683
14877
|
const parsedProject = parseProjectGodot(rawProject);
|
|
14684
14878
|
return {
|
|
14685
14879
|
mimeType: "application/json",
|
|
@@ -14688,7 +14882,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
14688
14882
|
}
|
|
14689
14883
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
14690
14884
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
14691
|
-
const text =
|
|
14885
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
14692
14886
|
return {
|
|
14693
14887
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
14694
14888
|
text
|
|
@@ -15157,7 +15351,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
15157
15351
|
},
|
|
15158
15352
|
{
|
|
15159
15353
|
name: "project_test",
|
|
15160
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case: which failed, where, and what the assertion said. The class list is rebuilt first, so a suite written a moment ago is found. Needs gdUnit4 under addons/gdUnit4.",
|
|
15354
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case: which failed, where, and what the assertion said. The class list is rebuilt first, so a suite written a moment ago is found. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. Needs gdUnit4 under addons/gdUnit4.",
|
|
15161
15355
|
parameters: {
|
|
15162
15356
|
projectPath: PROJECT_PATH,
|
|
15163
15357
|
path: {
|
|
@@ -34297,7 +34491,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
34297
34491
|
|
|
34298
34492
|
// src/godot-path.ts
|
|
34299
34493
|
import { execFile as execFile4 } from "node:child_process";
|
|
34300
|
-
import { existsSync as
|
|
34494
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
34301
34495
|
import { normalize as normalize2 } from "node:path";
|
|
34302
34496
|
import { promisify as promisify4 } from "node:util";
|
|
34303
34497
|
var run4, GodotLocator2;
|
|
@@ -34339,7 +34533,7 @@ var init_godot_path = __esm(() => {
|
|
|
34339
34533
|
return known;
|
|
34340
34534
|
}
|
|
34341
34535
|
let ok = false;
|
|
34342
|
-
if (path === "godot" ||
|
|
34536
|
+
if (path === "godot" || existsSync8(path)) {
|
|
34343
34537
|
try {
|
|
34344
34538
|
await run4(path, ["--version"]);
|
|
34345
34539
|
ok = true;
|
|
@@ -35142,8 +35336,8 @@ var init_lsp_client = __esm(() => {
|
|
|
35142
35336
|
});
|
|
35143
35337
|
|
|
35144
35338
|
// src/project-scan.ts
|
|
35145
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
35146
|
-
import { join as
|
|
35339
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
35340
|
+
import { join as join10 } from "node:path";
|
|
35147
35341
|
function projectStructure(projectPath) {
|
|
35148
35342
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
35149
35343
|
const visit = (directory) => {
|
|
@@ -35152,7 +35346,7 @@ function projectStructure(projectPath) {
|
|
|
35152
35346
|
continue;
|
|
35153
35347
|
}
|
|
35154
35348
|
if (entry.isDirectory()) {
|
|
35155
|
-
visit(
|
|
35349
|
+
visit(join10(directory, entry.name));
|
|
35156
35350
|
} else if (entry.isFile()) {
|
|
35157
35351
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
35158
35352
|
if (extension === "tscn") {
|
|
@@ -35194,7 +35388,7 @@ function searchProject(projectPath, options) {
|
|
|
35194
35388
|
if (SKIPPED.has(entry.name)) {
|
|
35195
35389
|
continue;
|
|
35196
35390
|
}
|
|
35197
|
-
const entryPath =
|
|
35391
|
+
const entryPath = join10(directory, entry.name);
|
|
35198
35392
|
if (entry.isDirectory()) {
|
|
35199
35393
|
visit(entryPath);
|
|
35200
35394
|
continue;
|
|
@@ -35205,7 +35399,7 @@ function searchProject(projectPath, options) {
|
|
|
35205
35399
|
}
|
|
35206
35400
|
result.summary.files_searched += 1;
|
|
35207
35401
|
const matches = [];
|
|
35208
|
-
for (const [index, line] of
|
|
35402
|
+
for (const [index, line] of readFileSync9(entryPath, "utf8").split(`
|
|
35209
35403
|
`).entries()) {
|
|
35210
35404
|
if (full()) {
|
|
35211
35405
|
break;
|
|
@@ -35233,17 +35427,25 @@ var init_project_scan = __esm(() => {
|
|
|
35233
35427
|
});
|
|
35234
35428
|
|
|
35235
35429
|
// src/runtime-client.ts
|
|
35236
|
-
import { existsSync as
|
|
35430
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync10, unlinkSync } from "node:fs";
|
|
35237
35431
|
import { createConnection as createConnection3 } from "node:net";
|
|
35238
|
-
import { tmpdir as
|
|
35239
|
-
import { join as
|
|
35432
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
35433
|
+
import { join as join11, resolve as resolve4 } from "node:path";
|
|
35240
35434
|
function runtimeDirectory(variables = process.env) {
|
|
35241
35435
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
35242
35436
|
if (explicit) {
|
|
35243
35437
|
return explicit;
|
|
35244
35438
|
}
|
|
35245
35439
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
35246
|
-
return
|
|
35440
|
+
return join11(perUser ?? tmpdir4(), "gdharness");
|
|
35441
|
+
}
|
|
35442
|
+
function runtimeDirectories(variables = process.env) {
|
|
35443
|
+
const candidates = [runtimeDirectory(variables)];
|
|
35444
|
+
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
35445
|
+
for (const base of fallbacks) {
|
|
35446
|
+
candidates.push(join11(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
35447
|
+
}
|
|
35448
|
+
return [...new Set(candidates.map((path) => resolve4(path)))];
|
|
35247
35449
|
}
|
|
35248
35450
|
function processAlive(pid) {
|
|
35249
35451
|
try {
|
|
@@ -35256,7 +35458,7 @@ function processAlive(pid) {
|
|
|
35256
35458
|
function parseAnnouncement(file, pid) {
|
|
35257
35459
|
let fields;
|
|
35258
35460
|
try {
|
|
35259
|
-
fields = asParams(JSON.parse(
|
|
35461
|
+
fields = asParams(JSON.parse(readFileSync10(file, "utf8")));
|
|
35260
35462
|
} catch {
|
|
35261
35463
|
return null;
|
|
35262
35464
|
}
|
|
@@ -35274,8 +35476,19 @@ function parseAnnouncement(file, pid) {
|
|
|
35274
35476
|
file
|
|
35275
35477
|
};
|
|
35276
35478
|
}
|
|
35277
|
-
function discoverRuntimes(
|
|
35278
|
-
|
|
35479
|
+
function discoverRuntimes(directories = runtimeDirectories()) {
|
|
35480
|
+
const found = new Map;
|
|
35481
|
+
for (const directory of directories) {
|
|
35482
|
+
for (const endpoint of announcedIn(directory)) {
|
|
35483
|
+
if (!found.has(endpoint.pid)) {
|
|
35484
|
+
found.set(endpoint.pid, endpoint);
|
|
35485
|
+
}
|
|
35486
|
+
}
|
|
35487
|
+
}
|
|
35488
|
+
return [...found.values()].sort((a, b) => b.pid - a.pid);
|
|
35489
|
+
}
|
|
35490
|
+
function announcedIn(directory) {
|
|
35491
|
+
if (!existsSync9(directory)) {
|
|
35279
35492
|
return [];
|
|
35280
35493
|
}
|
|
35281
35494
|
const found = [];
|
|
@@ -35284,7 +35497,7 @@ function discoverRuntimes(directory = runtimeDirectory()) {
|
|
|
35284
35497
|
if (!match) {
|
|
35285
35498
|
continue;
|
|
35286
35499
|
}
|
|
35287
|
-
const file =
|
|
35500
|
+
const file = join11(directory, entry);
|
|
35288
35501
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
35289
35502
|
const endpoint = processAlive(pid) ? parseAnnouncement(file, pid) : null;
|
|
35290
35503
|
if (endpoint) {
|
|
@@ -35295,7 +35508,7 @@ function discoverRuntimes(directory = runtimeDirectory()) {
|
|
|
35295
35508
|
} catch {}
|
|
35296
35509
|
}
|
|
35297
35510
|
}
|
|
35298
|
-
return found
|
|
35511
|
+
return found;
|
|
35299
35512
|
}
|
|
35300
35513
|
function describe2(endpoint) {
|
|
35301
35514
|
return `pid ${endpoint.pid} on ${endpoint.address}:${endpoint.port} (${endpoint.project.name || "unnamed"} at ${endpoint.project.path})`;
|
|
@@ -35420,174 +35633,6 @@ var init_runtime_client = __esm(() => {
|
|
|
35420
35633
|
ANNOUNCEMENT_PATTERN = /^runtime-(\d+)\.json$/;
|
|
35421
35634
|
});
|
|
35422
35635
|
|
|
35423
|
-
// src/update-check.ts
|
|
35424
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
35425
|
-
import { homedir as homedir3, tmpdir as tmpdir4 } from "node:os";
|
|
35426
|
-
import { join as join11 } from "node:path";
|
|
35427
|
-
function cacheDirectory(environment) {
|
|
35428
|
-
const set = (name) => {
|
|
35429
|
-
const value = environment[name];
|
|
35430
|
-
return value !== undefined && value !== "" ? value : null;
|
|
35431
|
-
};
|
|
35432
|
-
const home = set("HOME") ?? homedir3();
|
|
35433
|
-
if (process.platform === "win32") {
|
|
35434
|
-
return join11(set("LOCALAPPDATA") ?? home, "gdharness");
|
|
35435
|
-
}
|
|
35436
|
-
if (process.platform === "darwin") {
|
|
35437
|
-
return join11(home, "Library", "Caches", "gdharness");
|
|
35438
|
-
}
|
|
35439
|
-
return join11(set("XDG_CACHE_HOME") ?? join11(home, ".cache"), "gdharness");
|
|
35440
|
-
}
|
|
35441
|
-
function cacheFile(environment = process.env) {
|
|
35442
|
-
try {
|
|
35443
|
-
const directory = cacheDirectory(environment);
|
|
35444
|
-
mkdirSync4(directory, { recursive: true, mode: 448 });
|
|
35445
|
-
return join11(directory, "update-check.json");
|
|
35446
|
-
} catch {
|
|
35447
|
-
return join11(tmpdir4(), "gdharness-update-check.json");
|
|
35448
|
-
}
|
|
35449
|
-
}
|
|
35450
|
-
function readCache(path) {
|
|
35451
|
-
try {
|
|
35452
|
-
if (!existsSync9(path)) {
|
|
35453
|
-
return null;
|
|
35454
|
-
}
|
|
35455
|
-
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
35456
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
35457
|
-
return null;
|
|
35458
|
-
}
|
|
35459
|
-
const record = parsed;
|
|
35460
|
-
const checkedAt = record["checkedAt"];
|
|
35461
|
-
const latest = record["latest"];
|
|
35462
|
-
if (typeof checkedAt !== "number" || typeof latest !== "string" || !VERSION.test(latest)) {
|
|
35463
|
-
return null;
|
|
35464
|
-
}
|
|
35465
|
-
return { checkedAt, latest };
|
|
35466
|
-
} catch {
|
|
35467
|
-
return null;
|
|
35468
|
-
}
|
|
35469
|
-
}
|
|
35470
|
-
function writeCache(path, entry) {
|
|
35471
|
-
try {
|
|
35472
|
-
writeFileSync6(path, JSON.stringify(entry), { encoding: "utf8", mode: 384 });
|
|
35473
|
-
} catch {}
|
|
35474
|
-
}
|
|
35475
|
-
function parts(version) {
|
|
35476
|
-
const withoutBuild = version.split("+")[0] ?? version;
|
|
35477
|
-
const dash = withoutBuild.indexOf("-");
|
|
35478
|
-
const numeric = dash === -1 ? withoutBuild : withoutBuild.slice(0, dash);
|
|
35479
|
-
return {
|
|
35480
|
-
numbers: numeric.split(".").map((piece) => Number.parseInt(piece, 10) || 0),
|
|
35481
|
-
prerelease: dash !== -1
|
|
35482
|
-
};
|
|
35483
|
-
}
|
|
35484
|
-
function isNewer(candidate, current) {
|
|
35485
|
-
const left = parts(candidate);
|
|
35486
|
-
const right = parts(current);
|
|
35487
|
-
for (let index = 0;index < 3; index += 1) {
|
|
35488
|
-
const a = left.numbers[index] ?? 0;
|
|
35489
|
-
const b = right.numbers[index] ?? 0;
|
|
35490
|
-
if (a !== b) {
|
|
35491
|
-
return a > b;
|
|
35492
|
-
}
|
|
35493
|
-
}
|
|
35494
|
-
return !left.prerelease && right.prerelease;
|
|
35495
|
-
}
|
|
35496
|
-
async function fetchLatest() {
|
|
35497
|
-
const response = await fetch(REGISTRY, {
|
|
35498
|
-
headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
|
|
35499
|
-
redirect: "error",
|
|
35500
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
35501
|
-
});
|
|
35502
|
-
if (!response.ok || response.body === null) {
|
|
35503
|
-
return null;
|
|
35504
|
-
}
|
|
35505
|
-
const declared = Number.parseInt(response.headers.get("content-length") ?? "", 10);
|
|
35506
|
-
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
35507
|
-
return null;
|
|
35508
|
-
}
|
|
35509
|
-
const chunks = [];
|
|
35510
|
-
let size = 0;
|
|
35511
|
-
for await (const chunk of response.body) {
|
|
35512
|
-
size += chunk.byteLength;
|
|
35513
|
-
if (size > MAX_BODY_BYTES) {
|
|
35514
|
-
return null;
|
|
35515
|
-
}
|
|
35516
|
-
chunks.push(chunk);
|
|
35517
|
-
}
|
|
35518
|
-
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
35519
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
35520
|
-
return null;
|
|
35521
|
-
}
|
|
35522
|
-
const version = parsed["version"];
|
|
35523
|
-
return typeof version === "string" && VERSION.test(version) ? version : null;
|
|
35524
|
-
}
|
|
35525
|
-
|
|
35526
|
-
class UpdateCheck {
|
|
35527
|
-
latest = null;
|
|
35528
|
-
checking = false;
|
|
35529
|
-
checkedAt = 0;
|
|
35530
|
-
retryAt = 0;
|
|
35531
|
-
backoffMs = FIRST_RETRY_MS;
|
|
35532
|
-
enabled;
|
|
35533
|
-
current;
|
|
35534
|
-
cachePath;
|
|
35535
|
-
constructor(current, environment = process.env) {
|
|
35536
|
-
this.current = current;
|
|
35537
|
-
this.enabled = (environment["GDHARNESS_NO_UPDATE_CHECK"] ?? "") === "";
|
|
35538
|
-
this.cachePath = cacheFile(environment);
|
|
35539
|
-
const cached = this.enabled ? readCache(this.cachePath) : null;
|
|
35540
|
-
if (cached !== null) {
|
|
35541
|
-
this.latest = cached.latest;
|
|
35542
|
-
this.checkedAt = cached.checkedAt;
|
|
35543
|
-
}
|
|
35544
|
-
}
|
|
35545
|
-
refresh(now = Date.now()) {
|
|
35546
|
-
if (!this.enabled || this.checking || now < this.retryAt || now - this.checkedAt < CACHE_MS) {
|
|
35547
|
-
return;
|
|
35548
|
-
}
|
|
35549
|
-
this.checking = true;
|
|
35550
|
-
fetchLatest().then((version) => {
|
|
35551
|
-
if (version === null) {
|
|
35552
|
-
this.scheduleRetry(now);
|
|
35553
|
-
return;
|
|
35554
|
-
}
|
|
35555
|
-
this.latest = version;
|
|
35556
|
-
this.checkedAt = Date.now();
|
|
35557
|
-
this.backoffMs = FIRST_RETRY_MS;
|
|
35558
|
-
this.retryAt = 0;
|
|
35559
|
-
writeCache(this.cachePath, { checkedAt: this.checkedAt, latest: version });
|
|
35560
|
-
}).catch(() => {
|
|
35561
|
-
this.scheduleRetry(now);
|
|
35562
|
-
}).finally(() => {
|
|
35563
|
-
this.checking = false;
|
|
35564
|
-
});
|
|
35565
|
-
}
|
|
35566
|
-
scheduleRetry(now) {
|
|
35567
|
-
this.retryAt = now + this.backoffMs;
|
|
35568
|
-
this.backoffMs = Math.min(this.backoffMs * 2, MAX_RETRY_MS);
|
|
35569
|
-
}
|
|
35570
|
-
notice() {
|
|
35571
|
-
const latest = this.latest;
|
|
35572
|
-
if (!this.enabled || latest === null || !isNewer(latest, this.current)) {
|
|
35573
|
-
return null;
|
|
35574
|
-
}
|
|
35575
|
-
return {
|
|
35576
|
-
current: this.current,
|
|
35577
|
-
latest,
|
|
35578
|
-
releaseNotes: `${RELEASES}/v${latest}`,
|
|
35579
|
-
upgrade: runLine(currentRunner(), latest, "upgrade")
|
|
35580
|
-
};
|
|
35581
|
-
}
|
|
35582
|
-
}
|
|
35583
|
-
var REGISTRY = "https://registry.npmjs.org/gdharness/latest", RELEASES = "https://github.com/Aureliolo/gdharness/releases/tag", CACHE_MS, REQUEST_TIMEOUT_MS = 1e4, MAX_BODY_BYTES, FIRST_RETRY_MS = 30000, MAX_RETRY_MS, VERSION;
|
|
35584
|
-
var init_update_check = __esm(() => {
|
|
35585
|
-
CACHE_MS = 4 * 60 * 60 * 1000;
|
|
35586
|
-
MAX_BODY_BYTES = 1 << 20;
|
|
35587
|
-
MAX_RETRY_MS = 60 * 60 * 1000;
|
|
35588
|
-
VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
35589
|
-
});
|
|
35590
|
-
|
|
35591
35636
|
// src/server.ts
|
|
35592
35637
|
import { execFile as execFile5, spawn } from "node:child_process";
|
|
35593
35638
|
import { existsSync as existsSync10, mkdtempSync as mkdtempSync3, readdirSync as readdirSync6, readFileSync as readFileSync11, realpathSync as realpathSync2, rmSync as rmSync6 } from "node:fs";
|
|
@@ -36390,7 +36435,8 @@ class GodotServer {
|
|
|
36390
36435
|
];
|
|
36391
36436
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
36392
36437
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
36393
|
-
const
|
|
36438
|
+
const userData = mkdtempSync3(join12(tmpdir5(), "gdharness-tests-"));
|
|
36439
|
+
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
36394
36440
|
const hung = await new Promise((resolve) => {
|
|
36395
36441
|
const timer = setTimeout(() => {
|
|
36396
36442
|
run.process.kill();
|
|
@@ -36418,6 +36464,7 @@ class GodotServer {
|
|
|
36418
36464
|
reportProblem = errorMessage(error);
|
|
36419
36465
|
} finally {
|
|
36420
36466
|
rmSync6(reportsDir, { recursive: true, force: true });
|
|
36467
|
+
rmSync6(userData, { recursive: true, force: true });
|
|
36421
36468
|
}
|
|
36422
36469
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries;
|
|
36423
36470
|
const verdicts = {
|
|
@@ -36528,7 +36575,7 @@ class GodotServer {
|
|
|
36528
36575
|
addonIsStale: status.connected ? stale : undefined,
|
|
36529
36576
|
bridgeAvailable: this.bridgeStartupError === null,
|
|
36530
36577
|
startupError: this.bridgeStartupError,
|
|
36531
|
-
staleNote: stale ?
|
|
36578
|
+
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION) : undefined,
|
|
36532
36579
|
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,
|
|
36533
36580
|
suggestion: isPortConflict ? "Stop duplicate gdharness/MCP server instances or re-run the command from the same server process that owns the bridge port." : undefined
|
|
36534
36581
|
};
|
|
@@ -36606,6 +36653,7 @@ class GodotServer {
|
|
|
36606
36653
|
addonVersion: now.addonVersion,
|
|
36607
36654
|
serverVersion: SERVER_VERSION,
|
|
36608
36655
|
addonIsStale: now.addonVersion !== SERVER_VERSION,
|
|
36656
|
+
staleNote: addonMismatch(now.addonVersion, SERVER_VERSION),
|
|
36609
36657
|
tookMs: Date.now() - began
|
|
36610
36658
|
});
|
|
36611
36659
|
}
|
|
@@ -36639,7 +36687,8 @@ class GodotServer {
|
|
|
36639
36687
|
this.logDebug(`Launching Godot editor for project: ${project.value.path}`);
|
|
36640
36688
|
const editor = spawn(engine.value, editorArguments(project.value.path), {
|
|
36641
36689
|
stdio: "ignore",
|
|
36642
|
-
detached: true
|
|
36690
|
+
detached: true,
|
|
36691
|
+
env: { ...process.env, GDHARNESS_RUNTIME_DIR: runtimeDirectory() }
|
|
36643
36692
|
});
|
|
36644
36693
|
const started = await new Promise((resolve) => {
|
|
36645
36694
|
editor.once("spawn", () => {
|
|
@@ -36772,8 +36821,8 @@ class GodotServer {
|
|
|
36772
36821
|
}
|
|
36773
36822
|
running.process?.kill();
|
|
36774
36823
|
}
|
|
36775
|
-
spawnGame(godotPath, cmdArgs) {
|
|
36776
|
-
const child = spawn(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
36824
|
+
spawnGame(godotPath, cmdArgs, env) {
|
|
36825
|
+
const child = spawn(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
36777
36826
|
const log = new GameLog;
|
|
36778
36827
|
const started = {
|
|
36779
36828
|
process: child,
|
|
@@ -38133,12 +38182,13 @@ class Ask {
|
|
|
38133
38182
|
}
|
|
38134
38183
|
|
|
38135
38184
|
// src/server-version.ts
|
|
38136
|
-
|
|
38185
|
+
init_update_check();
|
|
38186
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
38137
38187
|
var DEBUG_MODE2 = process.env["DEBUG"] === "true";
|
|
38138
38188
|
var GODOT_DEBUG_MODE_DEFAULT2 = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE2;
|
|
38139
38189
|
var SERVER_VERSION2 = (() => {
|
|
38140
38190
|
try {
|
|
38141
|
-
const pkg = JSON.parse(
|
|
38191
|
+
const pkg = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
|
|
38142
38192
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
38143
38193
|
} catch {
|
|
38144
38194
|
return "0.0.0";
|
|
@@ -38150,8 +38200,8 @@ init_class_cache();
|
|
|
38150
38200
|
init_headless();
|
|
38151
38201
|
init_resources();
|
|
38152
38202
|
init_server_version();
|
|
38153
|
-
import { cpSync, existsSync as
|
|
38154
|
-
import { dirname as dirname2, join as
|
|
38203
|
+
import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
38204
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
38155
38205
|
import { fileURLToPath } from "node:url";
|
|
38156
38206
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
38157
38207
|
var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
|
|
@@ -38161,24 +38211,24 @@ var RUNTIME_AUTOLOAD = {
|
|
|
38161
38211
|
};
|
|
38162
38212
|
var VERSION_MARKER = ".gdharness-version";
|
|
38163
38213
|
function shippedAddonsDirectory() {
|
|
38164
|
-
return
|
|
38214
|
+
return join7(dirname2(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
38165
38215
|
}
|
|
38166
38216
|
function shippedOperationsScript() {
|
|
38167
|
-
return
|
|
38217
|
+
return join7(dirname2(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
38168
38218
|
}
|
|
38169
38219
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
38170
38220
|
const installed = [];
|
|
38171
38221
|
for (const name of ADDONS) {
|
|
38172
|
-
const source =
|
|
38173
|
-
if (!
|
|
38222
|
+
const source = join7(from, name);
|
|
38223
|
+
if (!existsSync6(join7(source, name === "gdharness_runtime" ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
38174
38224
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
38175
38225
|
}
|
|
38176
|
-
const target =
|
|
38177
|
-
const replaced =
|
|
38226
|
+
const target = join7(projectPath, "addons", name);
|
|
38227
|
+
const replaced = existsSync6(target);
|
|
38178
38228
|
rmSync4(target, { recursive: true, force: true });
|
|
38179
|
-
|
|
38229
|
+
mkdirSync3(dirname2(target), { recursive: true });
|
|
38180
38230
|
cpSync(source, target, { recursive: true });
|
|
38181
|
-
|
|
38231
|
+
writeFileSync5(join7(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
38182
38232
|
`);
|
|
38183
38233
|
installed.push({ name, path: target, replaced });
|
|
38184
38234
|
}
|
|
@@ -38187,8 +38237,8 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
|
38187
38237
|
function removeAddons(projectPath) {
|
|
38188
38238
|
const removed = [];
|
|
38189
38239
|
for (const name of ADDONS) {
|
|
38190
|
-
const target =
|
|
38191
|
-
if (
|
|
38240
|
+
const target = join7(projectPath, "addons", name);
|
|
38241
|
+
if (existsSync6(target)) {
|
|
38192
38242
|
rmSync4(target, { recursive: true, force: true });
|
|
38193
38243
|
removed.push(target);
|
|
38194
38244
|
}
|
|
@@ -38222,10 +38272,10 @@ function enabledPlugins(settings) {
|
|
|
38222
38272
|
function inspectProject(projectPath) {
|
|
38223
38273
|
const problems = [];
|
|
38224
38274
|
const addons = ADDONS.map((name) => {
|
|
38225
|
-
const directory =
|
|
38226
|
-
const marker =
|
|
38227
|
-
const installed =
|
|
38228
|
-
const version =
|
|
38275
|
+
const directory = join7(projectPath, "addons", name);
|
|
38276
|
+
const marker = join7(directory, VERSION_MARKER);
|
|
38277
|
+
const installed = existsSync6(directory);
|
|
38278
|
+
const version = existsSync6(marker) ? readFileSync7(marker, "utf8").trim() : null;
|
|
38229
38279
|
const current = version === SERVER_VERSION;
|
|
38230
38280
|
if (!installed) {
|
|
38231
38281
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -38234,7 +38284,7 @@ function inspectProject(projectPath) {
|
|
|
38234
38284
|
}
|
|
38235
38285
|
return { name, installed, version, current };
|
|
38236
38286
|
});
|
|
38237
|
-
const settings = parseProjectGodot(
|
|
38287
|
+
const settings = parseProjectGodot(readFileSync7(join7(projectPath, "project.godot"), "utf8"));
|
|
38238
38288
|
const pluginsEnabled = enabledPlugins(settings);
|
|
38239
38289
|
for (const name of EDITOR_PLUGINS) {
|
|
38240
38290
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -38263,8 +38313,8 @@ function inspectProject(projectPath) {
|
|
|
38263
38313
|
|
|
38264
38314
|
// src/skill.ts
|
|
38265
38315
|
init_tool_definitions();
|
|
38266
|
-
import { existsSync as
|
|
38267
|
-
import { basename, dirname as dirname3, join as
|
|
38316
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
38317
|
+
import { basename, dirname as dirname3, join as join8 } from "node:path";
|
|
38268
38318
|
|
|
38269
38319
|
// src/tool-reference.ts
|
|
38270
38320
|
init_tool_definitions();
|
|
@@ -38311,7 +38361,7 @@ function renderToolsMarkdown() {
|
|
|
38311
38361
|
|
|
38312
38362
|
// src/skill.ts
|
|
38313
38363
|
var SKILL_NAME = "gdharness";
|
|
38314
|
-
var SHARED_SKILLS =
|
|
38364
|
+
var SHARED_SKILLS = join8(".agents", "skills");
|
|
38315
38365
|
function skillMarkdown(version) {
|
|
38316
38366
|
return `---
|
|
38317
38367
|
name: ${SKILL_NAME}
|
|
@@ -38333,7 +38383,8 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
38333
38383
|
- Every call takes \`projectPath\`, except \`runtime_*\` and \`debug_*\`, where it picks between
|
|
38334
38384
|
running games. There is no ambient project.
|
|
38335
38385
|
- \`editor_status\` says whether an editor is connected and whether its addon matches the server.
|
|
38336
|
-
\`addonIsStale\` means
|
|
38386
|
+
\`addonIsStale\` means they differ, and \`staleNote\` says which half is behind: an editor that
|
|
38387
|
+
needs restarting, or a server that needs reconnecting in your harness.
|
|
38337
38388
|
- Start the game with \`editor_run start\`, never by spawning an engine. The editor plays it, so
|
|
38338
38389
|
its debugger holds it, which is what gives the \`debug_*\` tools something to talk to.
|
|
38339
38390
|
- Read \`editor_output\` after every run. It returns the engine's errors and warnings as entries
|
|
@@ -38392,27 +38443,27 @@ whatever the engine said on stderr comes back under \`engine_messages\`.
|
|
|
38392
38443
|
function skillFiles(version) {
|
|
38393
38444
|
return new Map([
|
|
38394
38445
|
["SKILL.md", skillMarkdown(version)],
|
|
38395
|
-
[
|
|
38446
|
+
[join8("references", "tools.md"), renderToolsMarkdown()]
|
|
38396
38447
|
]);
|
|
38397
38448
|
}
|
|
38398
38449
|
function skillDirectories(harnesses, projectPath, exists) {
|
|
38399
|
-
const directories = [
|
|
38450
|
+
const directories = [join8(projectPath, SHARED_SKILLS, SKILL_NAME)];
|
|
38400
38451
|
for (const harness of harnesses) {
|
|
38401
38452
|
if (harness.skills === undefined) {
|
|
38402
38453
|
continue;
|
|
38403
38454
|
}
|
|
38404
|
-
const own =
|
|
38405
|
-
if ((!harness.skills.shared || exists(own)) && !directories.includes(
|
|
38406
|
-
directories.push(
|
|
38455
|
+
const own = join8(projectPath, harness.skills.dir);
|
|
38456
|
+
if ((!harness.skills.shared || exists(own)) && !directories.includes(join8(own, SKILL_NAME))) {
|
|
38457
|
+
directories.push(join8(own, SKILL_NAME));
|
|
38407
38458
|
}
|
|
38408
38459
|
}
|
|
38409
38460
|
return directories;
|
|
38410
38461
|
}
|
|
38411
38462
|
function everySkillDirectory(projectPath, harnesses) {
|
|
38412
|
-
const directories = new Set([
|
|
38463
|
+
const directories = new Set([join8(projectPath, SHARED_SKILLS, SKILL_NAME)]);
|
|
38413
38464
|
for (const harness of harnesses) {
|
|
38414
38465
|
if (harness.skills !== undefined) {
|
|
38415
|
-
directories.add(
|
|
38466
|
+
directories.add(join8(projectPath, harness.skills.dir, SKILL_NAME));
|
|
38416
38467
|
}
|
|
38417
38468
|
}
|
|
38418
38469
|
return [...directories];
|
|
@@ -38420,7 +38471,7 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
38420
38471
|
function removeSkill(directories, projectPath) {
|
|
38421
38472
|
const removed = [];
|
|
38422
38473
|
for (const directory of directories) {
|
|
38423
|
-
if (
|
|
38474
|
+
if (existsSync7(directory)) {
|
|
38424
38475
|
rmSync5(directory, { recursive: true, force: true });
|
|
38425
38476
|
pruneEmpty(dirname3(directory), projectPath);
|
|
38426
38477
|
removed.push(directory);
|
|
@@ -38429,7 +38480,7 @@ function removeSkill(directories, projectPath) {
|
|
|
38429
38480
|
return removed;
|
|
38430
38481
|
}
|
|
38431
38482
|
function pruneEmpty(directory, projectPath) {
|
|
38432
|
-
const shared =
|
|
38483
|
+
const shared = join8(projectPath, ".agents");
|
|
38433
38484
|
let path = directory;
|
|
38434
38485
|
while (path !== projectPath && (basename(path) === "skills" || path === shared)) {
|
|
38435
38486
|
if (readdirSync3(path).length > 0) {
|
|
@@ -38442,30 +38493,30 @@ function pruneEmpty(directory, projectPath) {
|
|
|
38442
38493
|
function writeSkill(directories, version) {
|
|
38443
38494
|
const files = skillFiles(version);
|
|
38444
38495
|
return directories.map((directory) => {
|
|
38445
|
-
const replaced =
|
|
38496
|
+
const replaced = existsSync7(directory);
|
|
38446
38497
|
rmSync5(directory, { recursive: true, force: true });
|
|
38447
38498
|
for (const [name, contents] of files) {
|
|
38448
|
-
const path =
|
|
38449
|
-
|
|
38450
|
-
|
|
38499
|
+
const path = join8(directory, name);
|
|
38500
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
38501
|
+
writeFileSync6(path, contents, "utf8");
|
|
38451
38502
|
}
|
|
38452
38503
|
return { path: directory, replaced };
|
|
38453
38504
|
});
|
|
38454
38505
|
}
|
|
38455
38506
|
|
|
38456
38507
|
// src/version.ts
|
|
38457
|
-
import { readFileSync as
|
|
38458
|
-
import { dirname as dirname4, join as
|
|
38508
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
38509
|
+
import { dirname as dirname4, join as join9 } from "node:path";
|
|
38459
38510
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
38460
38511
|
function getLocalVersion() {
|
|
38461
38512
|
const currentDirectory = dirname4(fileURLToPath2(import.meta.url));
|
|
38462
38513
|
const packageCandidates = [
|
|
38463
|
-
|
|
38464
|
-
|
|
38514
|
+
join9(currentDirectory, "..", "package.json"),
|
|
38515
|
+
join9(currentDirectory, "..", "..", "package.json")
|
|
38465
38516
|
];
|
|
38466
38517
|
for (const packagePath of packageCandidates) {
|
|
38467
38518
|
try {
|
|
38468
|
-
const value = JSON.parse(
|
|
38519
|
+
const value = JSON.parse(readFileSync8(packagePath, "utf-8"));
|
|
38469
38520
|
if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
|
|
38470
38521
|
return value.version;
|
|
38471
38522
|
}
|