gdharness 1.1.21 → 1.1.22
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 +1036 -525
- package/build/godot/addons/gdharness_editor/property_values.gd +484 -65
- package/build/godot/addons/gdharness_editor/reading.gd +17 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +229 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/serialisation.gd +116 -7
- package/build/godot/addons/gdharness_editor/tool_executor.gd +10 -3
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +374 -242
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +47 -9
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +206 -0
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +90 -15
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +373 -140
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +566 -472
- package/build/godot/addons/gdharness_runtime/call_errors.gd +42 -0
- package/build/godot/addons/gdharness_runtime/call_errors.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +17 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +8 -4
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +440 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +184 -338
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +28 -8
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +7 -299
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +13 -1
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +90 -24
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +351 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +92 -33
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +20 -9
- package/build/godot/addons/gdharness_runtime/serialisation.gd +116 -7
- package/build/godot/operations/audio_buses.gd +47 -11
- package/build/godot/operations/class_cache.gd +18 -2
- package/build/godot/operations/dependencies.gd +128 -17
- package/build/godot/operations/file_walk.gd +20 -0
- package/build/godot/operations/gdscript_analysis.gd +59 -20
- package/build/godot/operations/gdscript_authoring.gd +127 -60
- package/build/godot/operations/godot_operations.gd +24 -5
- package/build/godot/operations/import_pipeline.gd +179 -19
- package/build/godot/operations/logger.gd +2 -2
- package/build/godot/operations/plugins.gd +88 -70
- package/build/godot/operations/project_config.gd +41 -6
- package/build/godot/operations/project_diagnostics.gd +25 -10
- package/build/godot/operations/reading.gd +17 -0
- package/build/godot/operations/resource_files.gd +60 -17
- package/build/godot/operations/serialisation.gd +116 -7
- package/build/index.js +949 -435
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -7712,6 +7712,14 @@ function alongside(execPath, name) {
|
|
|
7712
7712
|
}
|
|
7713
7713
|
var init_runner = () => {};
|
|
7714
7714
|
|
|
7715
|
+
// src/dictionary.ts
|
|
7716
|
+
function emptyRecord() {
|
|
7717
|
+
return Object.create(null);
|
|
7718
|
+
}
|
|
7719
|
+
function dictionary(entries) {
|
|
7720
|
+
return Object.assign(emptyRecord(), entries);
|
|
7721
|
+
}
|
|
7722
|
+
|
|
7715
7723
|
// src/call-signal.ts
|
|
7716
7724
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7717
7725
|
function withCallSignal(signal, answer) {
|
|
@@ -7725,14 +7733,6 @@ var init_call_signal = __esm(() => {
|
|
|
7725
7733
|
current = new AsyncLocalStorage;
|
|
7726
7734
|
});
|
|
7727
7735
|
|
|
7728
|
-
// src/dictionary.ts
|
|
7729
|
-
function emptyRecord() {
|
|
7730
|
-
return Object.create(null);
|
|
7731
|
-
}
|
|
7732
|
-
function dictionary(entries) {
|
|
7733
|
-
return Object.assign(emptyRecord(), entries);
|
|
7734
|
-
}
|
|
7735
|
-
|
|
7736
7736
|
// src/game-log.ts
|
|
7737
7737
|
import { StringDecoder } from "node:string_decoder";
|
|
7738
7738
|
function announced(line) {
|
|
@@ -7850,6 +7850,51 @@ var init_game_log = __esm(() => {
|
|
|
7850
7850
|
};
|
|
7851
7851
|
});
|
|
7852
7852
|
|
|
7853
|
+
// src/engine-run.ts
|
|
7854
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7855
|
+
function runEngine(executable, args, options = {}) {
|
|
7856
|
+
return new Promise((resolve) => {
|
|
7857
|
+
const log = new GameLog;
|
|
7858
|
+
let failure = null;
|
|
7859
|
+
const signal = callSignal();
|
|
7860
|
+
const child = spawn2(executable, [...args], {
|
|
7861
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7862
|
+
windowsHide: true,
|
|
7863
|
+
...signal === undefined ? {} : { signal }
|
|
7864
|
+
});
|
|
7865
|
+
child.stdout.on("data", (chunk) => {
|
|
7866
|
+
log.append("stdout", chunk);
|
|
7867
|
+
});
|
|
7868
|
+
child.stderr.on("data", (chunk) => {
|
|
7869
|
+
log.append("stderr", chunk);
|
|
7870
|
+
});
|
|
7871
|
+
const { timeout } = options;
|
|
7872
|
+
const timer = timeout === undefined ? undefined : setTimeout(() => {
|
|
7873
|
+
failure ??= `it ran past its ${timeout.said}`;
|
|
7874
|
+
child.kill();
|
|
7875
|
+
}, timeout.ms);
|
|
7876
|
+
child.on("error", (error) => {
|
|
7877
|
+
failure ??= error.name === "AbortError" ? "the call was cancelled" : `it could not be started: ${error.message}`;
|
|
7878
|
+
});
|
|
7879
|
+
child.on("close", (code, closedBy) => {
|
|
7880
|
+
clearTimeout(timer);
|
|
7881
|
+
log.finish();
|
|
7882
|
+
const started = child.pid !== undefined;
|
|
7883
|
+
resolve({ log, exitCode: started ? code : null, exitSignal: closedBy, failure });
|
|
7884
|
+
});
|
|
7885
|
+
});
|
|
7886
|
+
}
|
|
7887
|
+
function howItEnded(run) {
|
|
7888
|
+
if (run.failure !== null) {
|
|
7889
|
+
return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
|
|
7890
|
+
}
|
|
7891
|
+
return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
|
|
7892
|
+
}
|
|
7893
|
+
var init_engine_run = __esm(() => {
|
|
7894
|
+
init_call_signal();
|
|
7895
|
+
init_game_log();
|
|
7896
|
+
});
|
|
7897
|
+
|
|
7853
7898
|
// src/reimport.ts
|
|
7854
7899
|
import { createHash } from "node:crypto";
|
|
7855
7900
|
import { existsSync as existsSync5, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync2, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
@@ -7993,10 +8038,8 @@ var init_scratch = __esm(() => {
|
|
|
7993
8038
|
});
|
|
7994
8039
|
|
|
7995
8040
|
// src/headless.ts
|
|
7996
|
-
import {
|
|
7997
|
-
import {
|
|
7998
|
-
import { join as join7 } from "node:path";
|
|
7999
|
-
import { promisify as promisify3 } from "node:util";
|
|
8041
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, realpathSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8042
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
8000
8043
|
function snakeCased(params) {
|
|
8001
8044
|
const result = emptyRecord();
|
|
8002
8045
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -8005,34 +8048,55 @@ function snakeCased(params) {
|
|
|
8005
8048
|
}
|
|
8006
8049
|
return result;
|
|
8007
8050
|
}
|
|
8008
|
-
function
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
return parsed;
|
|
8015
|
-
}
|
|
8016
|
-
} catch {}
|
|
8017
|
-
}
|
|
8018
|
-
return null;
|
|
8051
|
+
function engineExtras(outcome) {
|
|
8052
|
+
return {
|
|
8053
|
+
...outcome.messages.length > 0 ? { engine_messages: outcome.messages } : {},
|
|
8054
|
+
...outcome.messagesOmitted === undefined ? {} : { engine_messages_omitted: outcome.messagesOmitted },
|
|
8055
|
+
...outcome.afterAnswer === undefined ? {} : { engine_after_answer: outcome.afterAnswer }
|
|
8056
|
+
};
|
|
8019
8057
|
}
|
|
8020
|
-
function
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8058
|
+
function problemsIn(log) {
|
|
8059
|
+
return log.everything().flatMap((entry) => {
|
|
8060
|
+
if (entry.severity !== "info") {
|
|
8061
|
+
return [entry];
|
|
8062
|
+
}
|
|
8063
|
+
if (entry.source === "stderr" && entry.text.startsWith(OWN_ERROR)) {
|
|
8064
|
+
return [{ ...entry, severity: "error", text: entry.text.slice(OWN_ERROR.length) }];
|
|
8065
|
+
}
|
|
8066
|
+
return [];
|
|
8067
|
+
});
|
|
8068
|
+
}
|
|
8069
|
+
function reported(problems) {
|
|
8070
|
+
const omitted = Math.max(0, problems.length - MESSAGES_KEPT);
|
|
8071
|
+
return omitted === 0 ? { messages: problems } : { messages: problems.slice(omitted), messagesOmitted: omitted };
|
|
8025
8072
|
}
|
|
8026
|
-
function reason(
|
|
8027
|
-
const own =
|
|
8073
|
+
function reason(log, problems) {
|
|
8074
|
+
const own = log.everything().filter((entry) => entry.source === "stderr" && entry.severity === "info" && entry.text.startsWith(OWN_ERROR));
|
|
8028
8075
|
if (own.length > 0) {
|
|
8029
|
-
return own.join("; ");
|
|
8076
|
+
return own.map((entry) => entry.text.slice(OWN_ERROR.length)).join("; ");
|
|
8030
8077
|
}
|
|
8031
|
-
const
|
|
8032
|
-
if (
|
|
8033
|
-
|
|
8078
|
+
const errors = problems.filter((entry) => entry.severity === "error").map((entry) => entry.text);
|
|
8079
|
+
if (errors.length > 0) {
|
|
8080
|
+
const rest = errors.length - ERRORS_NAMED;
|
|
8081
|
+
return rest > 0 ? `${errors.slice(0, ERRORS_NAMED).join("; ")}; and ${rest} more error${rest === 1 ? "" : "s"}` : errors.join("; ");
|
|
8082
|
+
}
|
|
8083
|
+
const printed = log.everything().filter((entry) => entry.source === "stdout");
|
|
8084
|
+
return printed.at(-1)?.text ?? "no output at all";
|
|
8085
|
+
}
|
|
8086
|
+
function scriptFault(problems, script) {
|
|
8087
|
+
const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
|
|
8088
|
+
const directories = [...new Set([spelled(dirname4(script)), spelled(resolvedOr(dirname4(script)))])];
|
|
8089
|
+
return problems.find((entry) => {
|
|
8090
|
+
const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
|
|
8091
|
+
return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
|
|
8092
|
+
});
|
|
8093
|
+
}
|
|
8094
|
+
function resolvedOr(path) {
|
|
8095
|
+
try {
|
|
8096
|
+
return realpathSync.native(path);
|
|
8097
|
+
} catch {
|
|
8098
|
+
return path;
|
|
8034
8099
|
}
|
|
8035
|
-
return stdout.trim().split(/\r?\n/).at(-1) ?? "no output at all";
|
|
8036
8100
|
}
|
|
8037
8101
|
function librariesNotCopied(messages) {
|
|
8038
8102
|
const found = new Set;
|
|
@@ -8072,30 +8136,33 @@ async function runImport(godotPath, projectPath, options = {}) {
|
|
|
8072
8136
|
}
|
|
8073
8137
|
async function importOnce(godotPath, projectPath) {
|
|
8074
8138
|
const logDir = scratchDirectory("import");
|
|
8139
|
+
let ran;
|
|
8075
8140
|
try {
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
messages: problems(failed.stderr)
|
|
8085
|
-
};
|
|
8086
|
-
}
|
|
8087
|
-
return {
|
|
8088
|
-
ok: false,
|
|
8089
|
-
message: `the import pass could not be run: ${error instanceof Error ? error.message : String(error)}`,
|
|
8090
|
-
messages: []
|
|
8091
|
-
};
|
|
8141
|
+
ran = await runEngine(godotPath, [
|
|
8142
|
+
"--headless",
|
|
8143
|
+
"--log-file",
|
|
8144
|
+
join7(logDir, "engine.log"),
|
|
8145
|
+
"--path",
|
|
8146
|
+
projectPath,
|
|
8147
|
+
"--import"
|
|
8148
|
+
]);
|
|
8092
8149
|
} finally {
|
|
8093
8150
|
discard(logDir);
|
|
8094
8151
|
}
|
|
8152
|
+
const problems = problemsIn(ran.log);
|
|
8153
|
+
if (ran.exitCode === 0 && ran.failure === null) {
|
|
8154
|
+
return { ok: true, ...reported(problems) };
|
|
8155
|
+
}
|
|
8156
|
+
return {
|
|
8157
|
+
ok: false,
|
|
8158
|
+
message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
|
|
8159
|
+
...reported(problems)
|
|
8160
|
+
};
|
|
8095
8161
|
}
|
|
8096
8162
|
async function runOperation(engine, operation, params, projectPath) {
|
|
8097
8163
|
const paramsDir = scratchDirectory("params");
|
|
8098
8164
|
const paramsFile = join7(paramsDir, `${operation}.json`);
|
|
8165
|
+
const answerFile = join7(paramsDir, "answer.json");
|
|
8099
8166
|
writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
8100
8167
|
const args = [
|
|
8101
8168
|
"--headless",
|
|
@@ -8107,50 +8174,73 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
8107
8174
|
engine.script,
|
|
8108
8175
|
operation,
|
|
8109
8176
|
`@file:${paramsFile}`,
|
|
8177
|
+
answerFile,
|
|
8110
8178
|
...engine.debug ? ["--debug-godot"] : []
|
|
8111
8179
|
];
|
|
8112
|
-
let
|
|
8113
|
-
let
|
|
8180
|
+
let ran;
|
|
8181
|
+
let answer;
|
|
8114
8182
|
try {
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
messages: problems(failed.stderr)
|
|
8123
|
-
};
|
|
8124
|
-
}
|
|
8183
|
+
ran = await runEngine(engine.godotPath, args);
|
|
8184
|
+
answer = answerIn(answerFile);
|
|
8185
|
+
} finally {
|
|
8186
|
+
discard(paramsDir);
|
|
8187
|
+
}
|
|
8188
|
+
const problems = problemsIn(ran.log);
|
|
8189
|
+
if (answer.kind === "unreadable") {
|
|
8125
8190
|
return {
|
|
8126
8191
|
ok: false,
|
|
8127
|
-
message: `${operation} could not be
|
|
8128
|
-
|
|
8192
|
+
message: `${operation} wrote an answer that could not be read (${answer.why})`,
|
|
8193
|
+
...reported(problems)
|
|
8194
|
+
};
|
|
8195
|
+
}
|
|
8196
|
+
if (answer.kind === "none") {
|
|
8197
|
+
const ending = ran.exitCode === 0 && ran.failure === null ? "produced no result" : `failed (${howItEnded(ran)})`;
|
|
8198
|
+
return {
|
|
8199
|
+
ok: false,
|
|
8200
|
+
message: `${operation} ${ending}: ${reason(ran.log, problems)}`,
|
|
8201
|
+
...reported(problems)
|
|
8129
8202
|
};
|
|
8130
|
-
} finally {
|
|
8131
|
-
discard(paramsDir);
|
|
8132
8203
|
}
|
|
8133
|
-
const
|
|
8134
|
-
if (
|
|
8204
|
+
const fault = scriptFault(problems, engine.script);
|
|
8205
|
+
if (fault !== undefined) {
|
|
8135
8206
|
return {
|
|
8136
8207
|
ok: false,
|
|
8137
|
-
message: `${operation}
|
|
8138
|
-
|
|
8208
|
+
message: `${operation} hit an error in the operations script, so its answer is left out: part of it was ` + `never computed. ${fault.text} (${fault.detail[0]?.slice("at: ".length) ?? "no location given"})`,
|
|
8209
|
+
...reported(problems)
|
|
8139
8210
|
};
|
|
8140
8211
|
}
|
|
8141
|
-
|
|
8212
|
+
const afterAnswer = ran.exitCode === 0 && ran.failure === null ? undefined : `The operation finished and wrote this answer, and then the engine did not exit cleanly (${howItEnded(ran)}). Anything it reported on the way out is under engine_messages.`;
|
|
8213
|
+
return {
|
|
8214
|
+
ok: true,
|
|
8215
|
+
payload: answer.payload,
|
|
8216
|
+
...reported(problems),
|
|
8217
|
+
...afterAnswer === undefined ? {} : { afterAnswer }
|
|
8218
|
+
};
|
|
8142
8219
|
}
|
|
8143
|
-
|
|
8220
|
+
function answerIn(file) {
|
|
8221
|
+
if (!existsSync7(file)) {
|
|
8222
|
+
return { kind: "none" };
|
|
8223
|
+
}
|
|
8224
|
+
const text = readFileSync4(file, "utf8");
|
|
8225
|
+
try {
|
|
8226
|
+
const parsed = JSON.parse(text);
|
|
8227
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
|
|
8228
|
+
} catch (error) {
|
|
8229
|
+
return {
|
|
8230
|
+
kind: "unreadable",
|
|
8231
|
+
why: `${error instanceof Error ? error.message : String(error)}: ${text.slice(0, 200)}`
|
|
8232
|
+
};
|
|
8233
|
+
}
|
|
8234
|
+
}
|
|
8235
|
+
var OWN_ERROR = "[ERROR] ", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
|
|
8144
8236
|
var init_headless = __esm(() => {
|
|
8145
|
-
|
|
8146
|
-
init_game_log();
|
|
8237
|
+
init_engine_run();
|
|
8147
8238
|
init_reimport();
|
|
8148
8239
|
init_scratch();
|
|
8149
|
-
run3 = promisify3(execFile3);
|
|
8150
8240
|
});
|
|
8151
8241
|
|
|
8152
8242
|
// src/update-check.ts
|
|
8153
|
-
import { existsSync as
|
|
8243
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8154
8244
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
8155
8245
|
import { join as join8 } from "node:path";
|
|
8156
8246
|
function packageUrl(root) {
|
|
@@ -8192,10 +8282,10 @@ function cacheFile(environment = process.env) {
|
|
|
8192
8282
|
}
|
|
8193
8283
|
function readCache(path) {
|
|
8194
8284
|
try {
|
|
8195
|
-
if (!
|
|
8285
|
+
if (!existsSync8(path)) {
|
|
8196
8286
|
return null;
|
|
8197
8287
|
}
|
|
8198
|
-
const parsed = JSON.parse(
|
|
8288
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
8199
8289
|
if (typeof parsed !== "object" || parsed === null) {
|
|
8200
8290
|
return null;
|
|
8201
8291
|
}
|
|
@@ -8343,7 +8433,7 @@ var init_update_check = __esm(() => {
|
|
|
8343
8433
|
});
|
|
8344
8434
|
|
|
8345
8435
|
// src/server-version.ts
|
|
8346
|
-
import { readFileSync as
|
|
8436
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
8347
8437
|
function editorIsStale(addonVersion, serverVersion, addonDigest, shippedDigest) {
|
|
8348
8438
|
if (addonDigest !== undefined && addonDigest !== "" && shippedDigest !== undefined) {
|
|
8349
8439
|
return addonDigest !== shippedDigest;
|
|
@@ -8385,7 +8475,7 @@ var init_server_version = __esm(() => {
|
|
|
8385
8475
|
GODOT_DEBUG_MODE_DEFAULT = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE;
|
|
8386
8476
|
SERVER_VERSION = (() => {
|
|
8387
8477
|
try {
|
|
8388
|
-
const pkg = JSON.parse(
|
|
8478
|
+
const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
|
|
8389
8479
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
8390
8480
|
} catch {
|
|
8391
8481
|
return "0.0.0";
|
|
@@ -8519,8 +8609,11 @@ var init_issues = __esm(() => {
|
|
|
8519
8609
|
});
|
|
8520
8610
|
|
|
8521
8611
|
// src/class-cache.ts
|
|
8522
|
-
import { existsSync as
|
|
8612
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "node:fs";
|
|
8523
8613
|
import { join as join9 } from "node:path";
|
|
8614
|
+
function isToolScript(source) {
|
|
8615
|
+
return TOOL.test(codeOf(source));
|
|
8616
|
+
}
|
|
8524
8617
|
function declaredClasses(projectPath) {
|
|
8525
8618
|
const declared = new Map;
|
|
8526
8619
|
eachScript(projectPath, (script, source) => {
|
|
@@ -8533,7 +8626,7 @@ function declaredClasses(projectPath) {
|
|
|
8533
8626
|
}
|
|
8534
8627
|
function eachScript(projectPath, each) {
|
|
8535
8628
|
const visit = (directory, prefix) => {
|
|
8536
|
-
if (
|
|
8629
|
+
if (existsSync9(join9(directory, ".gdignore"))) {
|
|
8537
8630
|
return;
|
|
8538
8631
|
}
|
|
8539
8632
|
for (const entry of readdirSync3(directory, { withFileTypes: true })) {
|
|
@@ -8544,7 +8637,7 @@ function eachScript(projectPath, each) {
|
|
|
8544
8637
|
if (entry.isDirectory()) {
|
|
8545
8638
|
visit(path, `${prefix}${entry.name}/`);
|
|
8546
8639
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
8547
|
-
each(`res://${prefix}${entry.name}`,
|
|
8640
|
+
each(`res://${prefix}${entry.name}`, readFileSync7(path, "utf8"));
|
|
8548
8641
|
}
|
|
8549
8642
|
}
|
|
8550
8643
|
};
|
|
@@ -8560,7 +8653,7 @@ function scriptsNaming(projectPath, classes) {
|
|
|
8560
8653
|
}
|
|
8561
8654
|
const code = codeOf(source);
|
|
8562
8655
|
if (classesNamedIn(code, named).length > 0) {
|
|
8563
|
-
naming.push({ script, tool:
|
|
8656
|
+
naming.push({ script, tool: TOOL.test(code) });
|
|
8564
8657
|
}
|
|
8565
8658
|
});
|
|
8566
8659
|
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
@@ -8594,11 +8687,11 @@ function codeOf(source) {
|
|
|
8594
8687
|
}
|
|
8595
8688
|
function cachedClasses(projectPath) {
|
|
8596
8689
|
const cache = join9(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
8597
|
-
if (!
|
|
8690
|
+
if (!existsSync9(cache)) {
|
|
8598
8691
|
return null;
|
|
8599
8692
|
}
|
|
8600
8693
|
const listed = new Map;
|
|
8601
|
-
const text =
|
|
8694
|
+
const text = readFileSync7(cache, "utf8");
|
|
8602
8695
|
for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
|
|
8603
8696
|
listed.set(entry[1] ?? "", entry[2] ?? "");
|
|
8604
8697
|
}
|
|
@@ -8767,6 +8860,19 @@ function classRemedy(entries) {
|
|
|
8767
8860
|
const oneAtATime = declaring.length === 1 ? "" : " Take them one at a time: a rescan that brings in a class also reloads every script naming it and lists them under dependentsReloaded, so a script listed there needs no call of its own.";
|
|
8768
8861
|
return `For ${entries.map(written).join(", ")}, run ${reload} first.${oneAtATime} In the one project ` + "that has reproduced a stale enum member, three times, the reload cleared it at once each time, " + "and the one plain editor_rescan tried left the diagnostic standing.";
|
|
8769
8862
|
}
|
|
8863
|
+
function notReloadedNote(broughtIn, notReloaded) {
|
|
8864
|
+
const tried = notReloaded.filter((one) => one.tool !== true).map((one) => one.scriptPath);
|
|
8865
|
+
const tools = notReloaded.filter((one) => one.tool === true).map((one) => one.scriptPath);
|
|
8866
|
+
const all = [...tried, ...tools];
|
|
8867
|
+
const remedies = [];
|
|
8868
|
+
if (tried.length > 0) {
|
|
8869
|
+
remedies.push(`For ${tried.join(", ")}: editor_rescan with reloadScript on ${tried.length === 1 ? "it" : "each"}, or editor_launch restart.`);
|
|
8870
|
+
}
|
|
8871
|
+
if (tools.length > 0) {
|
|
8872
|
+
remedies.push(`For ${tools.join(", ")}, ${tools.length === 1 ? "a @tool script" : "@tool scripts"}, which ${tools.length === 1 ? "runs" : "run"} inside the editor and ${tools.length === 1 ? "is" : "are"} not reloaded from inside a call: editor_launch restart.`);
|
|
8873
|
+
}
|
|
8874
|
+
return `The scan brought in ${broughtIn.join(", ")}, and ${all.join(", ")} ${all.length === 1 ? "names one of them and" : "name them and"} could not be reloaded, so the editor still holds what it compiled while it could not see the class and reports that to the language server. ${remedies.join(" ")}`;
|
|
8875
|
+
}
|
|
8770
8876
|
function failedReloadNote(failed) {
|
|
8771
8877
|
const missing = (before, after) => before.filter((name) => !after.includes(name));
|
|
8772
8878
|
const lost = [
|
|
@@ -8775,7 +8881,17 @@ function failedReloadNote(failed) {
|
|
|
8775
8881
|
];
|
|
8776
8882
|
const unchanged = lost.length === 0 && missing(failed.heldAfter, failed.heldBefore).length === 0 && missing(failed.constantsAfter, failed.constantsBefore).length === 0;
|
|
8777
8883
|
const answered = failed.codeName === "" ? `error ${failed.code}` : `error ${failed.code}, ${failed.codeName}`;
|
|
8778
|
-
const
|
|
8884
|
+
const unusable = "A failed compile leaves the copy the editor holds unusable, measured on 4.7.2: the editor can make no instance of it, and one it already made has lost its methods, while its member lists read as before.";
|
|
8885
|
+
let copy;
|
|
8886
|
+
if (failed.restored === null) {
|
|
8887
|
+
copy = `${unusable} This editor's addon does not put it back, so it stays that way until the script compiles: reload it then, or restart the editor with editor_launch restart.`;
|
|
8888
|
+
} else if (!failed.restored) {
|
|
8889
|
+
copy = `${unusable} The editor compiled it again from the text it was built from, and that failed too${failed.restoredAs === "" ? "" : ` (${failed.restoredAs})`}, so it stays that way until the script compiles: reload it then, or restart the editor with editor_launch restart.`;
|
|
8890
|
+
} else if (unchanged) {
|
|
8891
|
+
copy = "The copy the editor holds was compiled again from the text it was built from, which puts it back as it was, with the members under heldBeforeReload and heldConstantsBeforeReload.";
|
|
8892
|
+
} else {
|
|
8893
|
+
copy = `The copy the editor holds was compiled again from the text it was built from and changed even so${lost.length === 0 ? "" : `, and has lost ${lost.join(", ")}`}: anything diagnosed against it now is answered from what is left, and reloading it once it compiles rebuilds it.`;
|
|
8894
|
+
}
|
|
8779
8895
|
const order = failed.names.length === 0 ? "" : ` If they deny a member of ${failed.names.join(" or ")} that its file declares, the editor's copy of that class is behind: reload that class first with reloadScript and then this script, the order that cleared it in the one project that has met this.`;
|
|
8780
8896
|
return `${failed.script} did not compile: Godot answered ${answered}. ${copy} ` + `script_diagnostics on it gives the errors.${order}`;
|
|
8781
8897
|
}
|
|
@@ -8808,7 +8924,7 @@ function declaredSince(projectPath, paths, names, since) {
|
|
|
8808
8924
|
});
|
|
8809
8925
|
}
|
|
8810
8926
|
function cachedAtMissingPaths(cached, projectPath) {
|
|
8811
|
-
return [...cached].filter(([, path]) => path.startsWith("res://") && !
|
|
8927
|
+
return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync9(join9(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
|
|
8812
8928
|
}
|
|
8813
8929
|
function staleAgainst(cached, projectPath) {
|
|
8814
8930
|
return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
|
|
@@ -8825,9 +8941,10 @@ function unseenByEditor(projectPath, editorHolds) {
|
|
|
8825
8941
|
const held = new Set(editorHolds);
|
|
8826
8942
|
return [...declaredClasses(projectPath)].filter(([name]) => !held.has(name)).map(([className, path]) => ({ className, path })).sort((one, other) => one.className < other.className ? -1 : one.className > other.className ? 1 : 0);
|
|
8827
8943
|
}
|
|
8828
|
-
var DECLARATION;
|
|
8944
|
+
var DECLARATION, TOOL;
|
|
8829
8945
|
var init_class_cache = __esm(() => {
|
|
8830
8946
|
DECLARATION = /^(?:@[a-z_]+(?:\([^)\n]*\))?\s+)*class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m;
|
|
8947
|
+
TOOL = /^[ \t]*(?:@[a-z_]+(?:\([^)\n]*\))?[ \t]+)*@tool\b/m;
|
|
8831
8948
|
});
|
|
8832
8949
|
|
|
8833
8950
|
// node_modules/zod/v4/core/util.js
|
|
@@ -15934,11 +16051,11 @@ var init_types = __esm(() => {
|
|
|
15934
16051
|
});
|
|
15935
16052
|
|
|
15936
16053
|
// src/paths.ts
|
|
15937
|
-
import { realpathSync } from "node:fs";
|
|
16054
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
15938
16055
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
15939
16056
|
function realPathOr(path) {
|
|
15940
16057
|
try {
|
|
15941
|
-
return
|
|
16058
|
+
return realpathSync2.native(path);
|
|
15942
16059
|
} catch {
|
|
15943
16060
|
return path;
|
|
15944
16061
|
}
|
|
@@ -15959,6 +16076,12 @@ function resolveWithinProject(projectPath, candidatePath) {
|
|
|
15959
16076
|
return { ok: false, reason: "Path contains a null byte." };
|
|
15960
16077
|
}
|
|
15961
16078
|
const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : trimmed;
|
|
16079
|
+
if (withoutScheme.startsWith("uid://")) {
|
|
16080
|
+
return {
|
|
16081
|
+
ok: false,
|
|
16082
|
+
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.`
|
|
16083
|
+
};
|
|
16084
|
+
}
|
|
15962
16085
|
const scheme = OTHER_SCHEME.exec(withoutScheme);
|
|
15963
16086
|
if (scheme) {
|
|
15964
16087
|
return {
|
|
@@ -15995,7 +16118,7 @@ var init_paths = __esm(() => {
|
|
|
15995
16118
|
});
|
|
15996
16119
|
|
|
15997
16120
|
// src/resources.ts
|
|
15998
|
-
import { readFileSync as
|
|
16121
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
15999
16122
|
import { extname, resolve as resolve2 } from "node:path";
|
|
16000
16123
|
function ensureProjectPath(getProjectPath) {
|
|
16001
16124
|
const projectPath = getProjectPath();
|
|
@@ -16161,7 +16284,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
16161
16284
|
const parsedUri = parseGodotUri(uri);
|
|
16162
16285
|
if (parsedUri.kind === "project-info") {
|
|
16163
16286
|
const projectFilePath = resolveProjectFile(projectPath, "project.godot");
|
|
16164
|
-
const rawProject =
|
|
16287
|
+
const rawProject = readFileSync8(projectFilePath, "utf-8");
|
|
16165
16288
|
const parsedProject = parseProjectGodot(rawProject);
|
|
16166
16289
|
return {
|
|
16167
16290
|
mimeType: "application/json",
|
|
@@ -16170,7 +16293,7 @@ function readResourceText(uri, getProjectPath) {
|
|
|
16170
16293
|
}
|
|
16171
16294
|
const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
|
|
16172
16295
|
ensureAllowedExtension(parsedUri.kind, filePath);
|
|
16173
|
-
const text =
|
|
16296
|
+
const text = readFileSync8(filePath, "utf-8");
|
|
16174
16297
|
return {
|
|
16175
16298
|
mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
|
|
16176
16299
|
text
|
|
@@ -16302,16 +16425,16 @@ import { spawnSync } from "node:child_process";
|
|
|
16302
16425
|
import { createHash as createHash3 } from "node:crypto";
|
|
16303
16426
|
import {
|
|
16304
16427
|
cpSync,
|
|
16305
|
-
existsSync as
|
|
16428
|
+
existsSync as existsSync10,
|
|
16306
16429
|
mkdirSync as mkdirSync4,
|
|
16307
16430
|
readdirSync as readdirSync4,
|
|
16308
|
-
readFileSync as
|
|
16431
|
+
readFileSync as readFileSync9,
|
|
16309
16432
|
rmdirSync,
|
|
16310
16433
|
rmSync as rmSync5,
|
|
16311
16434
|
statSync as statSync4,
|
|
16312
16435
|
writeFileSync as writeFileSync6
|
|
16313
16436
|
} from "node:fs";
|
|
16314
|
-
import { dirname as
|
|
16437
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
16315
16438
|
import { fileURLToPath } from "node:url";
|
|
16316
16439
|
function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
16317
16440
|
const hash = createHash3("sha256");
|
|
@@ -16327,7 +16450,7 @@ function editorAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
16327
16450
|
} else {
|
|
16328
16451
|
hash.update(`${inside}
|
|
16329
16452
|
`);
|
|
16330
|
-
hash.update(
|
|
16453
|
+
hash.update(readFileSync9(path));
|
|
16331
16454
|
hash.update(`
|
|
16332
16455
|
`);
|
|
16333
16456
|
}
|
|
@@ -16350,29 +16473,29 @@ function shippedEditorDigest() {
|
|
|
16350
16473
|
}
|
|
16351
16474
|
function installedEditorDigest(projectPath) {
|
|
16352
16475
|
const marker = join10(projectPath, "addons", ADDONS[0], DIGEST_MARKER);
|
|
16353
|
-
if (!
|
|
16476
|
+
if (!existsSync10(marker)) {
|
|
16354
16477
|
return null;
|
|
16355
16478
|
}
|
|
16356
|
-
const said =
|
|
16479
|
+
const said = readFileSync9(marker, "utf8").trim();
|
|
16357
16480
|
return said === "" ? null : said;
|
|
16358
16481
|
}
|
|
16359
16482
|
function shippedAddonsDirectory() {
|
|
16360
|
-
return join10(
|
|
16483
|
+
return join10(dirname5(fileURLToPath(import.meta.url)), "godot", "addons");
|
|
16361
16484
|
}
|
|
16362
16485
|
function shippedOperationsScript() {
|
|
16363
|
-
return join10(
|
|
16486
|
+
return join10(dirname5(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
|
|
16364
16487
|
}
|
|
16365
16488
|
function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
16366
16489
|
const installed = [];
|
|
16367
16490
|
for (const name of ADDONS) {
|
|
16368
16491
|
const source = join10(from, name);
|
|
16369
|
-
if (!
|
|
16492
|
+
if (!existsSync10(join10(source, name === "gdharness_runtime" ? "runtime_autoload.gd" : "plugin.cfg"))) {
|
|
16370
16493
|
throw new Error(`The package holds no ${name} addon at ${source}.`);
|
|
16371
16494
|
}
|
|
16372
16495
|
const target = join10(projectPath, "addons", name);
|
|
16373
|
-
const replaced =
|
|
16496
|
+
const replaced = existsSync10(target);
|
|
16374
16497
|
rmSync5(target, { recursive: true, force: true });
|
|
16375
|
-
mkdirSync4(
|
|
16498
|
+
mkdirSync4(dirname5(target), { recursive: true });
|
|
16376
16499
|
cpSync(source, target, { recursive: true });
|
|
16377
16500
|
writeFileSync6(join10(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
16378
16501
|
`);
|
|
@@ -16384,23 +16507,23 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
|
16384
16507
|
}
|
|
16385
16508
|
function installedAddonVersion(projectPath) {
|
|
16386
16509
|
const marker = join10(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
16387
|
-
if (!
|
|
16510
|
+
if (!existsSync10(marker)) {
|
|
16388
16511
|
return null;
|
|
16389
16512
|
}
|
|
16390
|
-
const said =
|
|
16513
|
+
const said = readFileSync9(marker, "utf8").trim();
|
|
16391
16514
|
return said === "" ? null : said;
|
|
16392
16515
|
}
|
|
16393
16516
|
function removeAddons(projectPath) {
|
|
16394
16517
|
const removed = [];
|
|
16395
16518
|
for (const name of ADDONS) {
|
|
16396
16519
|
const target = join10(projectPath, "addons", name);
|
|
16397
|
-
if (
|
|
16520
|
+
if (existsSync10(target)) {
|
|
16398
16521
|
rmSync5(target, { recursive: true, force: true });
|
|
16399
16522
|
removed.push(target);
|
|
16400
16523
|
}
|
|
16401
16524
|
}
|
|
16402
16525
|
const addons = join10(projectPath, "addons");
|
|
16403
|
-
if (removed.length > 0 &&
|
|
16526
|
+
if (removed.length > 0 && existsSync10(addons) && readdirSync4(addons).length === 0) {
|
|
16404
16527
|
rmdirSync(addons);
|
|
16405
16528
|
removed.push(addons);
|
|
16406
16529
|
}
|
|
@@ -16424,7 +16547,7 @@ function autoloadIsOurs(named) {
|
|
|
16424
16547
|
return named === null || named === `res://${RUNTIME_AUTOLOAD.path}`;
|
|
16425
16548
|
}
|
|
16426
16549
|
async function setRuntime(engine, projectPath, on) {
|
|
16427
|
-
return on ? await runOperation(engine, "add_autoload", { name: RUNTIME_AUTOLOAD.name, path: `res://${RUNTIME_AUTOLOAD.path}`,
|
|
16550
|
+
return on ? await runOperation(engine, "add_autoload", { name: RUNTIME_AUTOLOAD.name, path: `res://${RUNTIME_AUTOLOAD.path}`, global: true }, projectPath) : await runOperation(engine, "remove_autoload", { name: RUNTIME_AUTOLOAD.name }, projectPath);
|
|
16428
16551
|
}
|
|
16429
16552
|
function loaderAmong(projectPath, named) {
|
|
16430
16553
|
let byName = null;
|
|
@@ -16434,7 +16557,7 @@ function loaderAmong(projectPath, named) {
|
|
|
16434
16557
|
}
|
|
16435
16558
|
let source = null;
|
|
16436
16559
|
try {
|
|
16437
|
-
source =
|
|
16560
|
+
source = readFileSync9(join10(projectPath, path), "utf8");
|
|
16438
16561
|
} catch {}
|
|
16439
16562
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
16440
16563
|
return { name: entry, path, exists: true };
|
|
@@ -16478,8 +16601,8 @@ function inspectProject(projectPath) {
|
|
|
16478
16601
|
const addons = ADDONS.map((name) => {
|
|
16479
16602
|
const directory = join10(projectPath, "addons", name);
|
|
16480
16603
|
const marker = join10(directory, VERSION_MARKER);
|
|
16481
|
-
const installed =
|
|
16482
|
-
const version =
|
|
16604
|
+
const installed = existsSync10(directory);
|
|
16605
|
+
const version = existsSync10(marker) ? readFileSync9(marker, "utf8").trim() : null;
|
|
16483
16606
|
const current = version === SERVER_VERSION;
|
|
16484
16607
|
if (!installed) {
|
|
16485
16608
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -16488,7 +16611,7 @@ function inspectProject(projectPath) {
|
|
|
16488
16611
|
}
|
|
16489
16612
|
return { name, installed, version, current };
|
|
16490
16613
|
});
|
|
16491
|
-
const settings = parseProjectGodot(
|
|
16614
|
+
const settings = parseProjectGodot(readFileSync9(join10(projectPath, "project.godot"), "utf8"));
|
|
16492
16615
|
const pluginsEnabled = enabledPlugins(settings);
|
|
16493
16616
|
for (const name of EDITOR_PLUGINS) {
|
|
16494
16617
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -16504,7 +16627,7 @@ function inspectProject(projectPath) {
|
|
|
16504
16627
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
16505
16628
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
16506
16629
|
for (const [name, path] of named) {
|
|
16507
|
-
if (!
|
|
16630
|
+
if (!existsSync10(join10(projectPath, path))) {
|
|
16508
16631
|
if (!addonMissing(path)) {
|
|
16509
16632
|
problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
|
|
16510
16633
|
}
|
|
@@ -16664,7 +16787,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16664
16787
|
};
|
|
16665
16788
|
PROPERTIES = {
|
|
16666
16789
|
type: "object",
|
|
16667
|
-
description: 'Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another.',
|
|
16790
|
+
description: 'Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. A property holding a node takes the path to it from the node being set, such as "../Player". Each value is read back once set, and one the engine changed or refused is refused with what it holds.',
|
|
16668
16791
|
additionalProperties: true
|
|
16669
16792
|
};
|
|
16670
16793
|
XY = {
|
|
@@ -16819,11 +16942,18 @@ var init_tool_definitions = __esm(() => {
|
|
|
16819
16942
|
},
|
|
16820
16943
|
value: {
|
|
16821
16944
|
blank: true,
|
|
16822
|
-
description: 'The value to write. Engine types may be tagged, {"_type": "Vector2", "x": 1, "y": 2}. "" writes an empty string.'
|
|
16945
|
+
description: 'The value to write. Engine types may be tagged, {"_type": "Vector2", "x": 1, "y": 2}, and infinity as {"_type": "float", "value": "inf"}, the form a read answers with. "" writes an empty string.'
|
|
16823
16946
|
},
|
|
16824
16947
|
name: { type: "string", description: "Autoload name." },
|
|
16825
16948
|
path: { type: "string", description: "Autoload script or scene inside the project." },
|
|
16826
|
-
enabled: {
|
|
16949
|
+
enabled: {
|
|
16950
|
+
type: "boolean",
|
|
16951
|
+
description: "Autoloads: Godot 4 loads every autoload in the list, so false is refused. Use global: false to keep the name out of the global scope, or remove_autoload to stop it loading."
|
|
16952
|
+
},
|
|
16953
|
+
global: {
|
|
16954
|
+
type: "boolean",
|
|
16955
|
+
description: `Autoloads: make the name a global variable, the * in project.godot and the editor's "Global Variable" column. Default true. The autoload loads either way.`
|
|
16956
|
+
},
|
|
16827
16957
|
scenePath: SCENE_PATH,
|
|
16828
16958
|
actionName: { type: "string", description: 'Input action name, such as "jump".' },
|
|
16829
16959
|
events: INPUT_EVENTS,
|
|
@@ -16835,9 +16965,15 @@ var init_tool_definitions = __esm(() => {
|
|
|
16835
16965
|
},
|
|
16836
16966
|
pluginName: { type: "string", description: "Folder name under addons/." },
|
|
16837
16967
|
busName: { type: "string", description: "Audio bus name." },
|
|
16838
|
-
parentBusIndex: {
|
|
16968
|
+
parentBusIndex: {
|
|
16969
|
+
type: "number",
|
|
16970
|
+
description: "Audio buses: the bus to send to. Default 0, Master. The new bus goes right after it, so every later bus moves one along, and the answer lists them all."
|
|
16971
|
+
},
|
|
16839
16972
|
busIndex: { type: "number", description: "Audio bus index." },
|
|
16840
|
-
effectIndex: {
|
|
16973
|
+
effectIndex: {
|
|
16974
|
+
type: "number",
|
|
16975
|
+
description: "Slot on the bus for the effect: the effect there is replaced, and one past the last adds one. Any other slot is refused."
|
|
16976
|
+
},
|
|
16841
16977
|
effectType: { type: "string", description: 'Effect class, such as "AudioEffectReverb".' },
|
|
16842
16978
|
volumeDb: { type: "number", description: "Bus volume in decibels." }
|
|
16843
16979
|
},
|
|
@@ -16856,7 +16992,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16856
16992
|
disable_plugin: { summary: "disable an addon", requires: ["pluginName"] },
|
|
16857
16993
|
add_audio_bus: { summary: "add an audio bus", requires: ["busName"] },
|
|
16858
16994
|
set_audio_bus_effect: {
|
|
16859
|
-
summary: "
|
|
16995
|
+
summary: "put an effect in a slot on a bus",
|
|
16860
16996
|
requires: ["busIndex", "effectIndex", "effectType"]
|
|
16861
16997
|
},
|
|
16862
16998
|
set_audio_bus_volume: { summary: "set a bus volume", requires: ["busIndex", "volumeDb"] }
|
|
@@ -16895,7 +17031,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16895
17031
|
type: "boolean",
|
|
16896
17032
|
description: "forward: include the engine's own res://. resources. Default false."
|
|
16897
17033
|
},
|
|
16898
|
-
fileTypes: {
|
|
17034
|
+
fileTypes: {
|
|
17035
|
+
type: "array",
|
|
17036
|
+
items: { type: "string" },
|
|
17037
|
+
description: 'reverse: extensions to look in, plus "godot" for project.godot, "cfg" for plugin.cfg and export_presets.cfg, and "import" for import sidecars. Default all of tscn, tres, gd, gdshader, godot, cfg and import, since an autoload, the main scene and a plugin script are named only in the settings files.'
|
|
17038
|
+
}
|
|
16899
17039
|
},
|
|
16900
17040
|
requires: ["projectPath", "resourcePath"]
|
|
16901
17041
|
},
|
|
@@ -16940,7 +17080,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
16940
17080
|
summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. Answered from a second status read: reimported lists what is current now, notReimported what is not, with its status and reason, and via says which of the two did the work. stillImporting is an editor that had not finished within timeoutMs",
|
|
16941
17081
|
requires: []
|
|
16942
17082
|
},
|
|
16943
|
-
uid: {
|
|
17083
|
+
uid: {
|
|
17084
|
+
summary: "the UID of one file and where it was read: a script's or shader's .uid file, an imported file's .import, or a scene's or resource's own header",
|
|
17085
|
+
requires: ["resourcePath"]
|
|
17086
|
+
},
|
|
16944
17087
|
refresh_uids: {
|
|
16945
17088
|
summary: "import the project so every script and shader has its .uid sidecar, and name under uidsCreated the ones this made and under stillWithoutUid the ones the engine would not import: it writes no scene and no script, so a project whose sidecars are all present is left untouched",
|
|
16946
17089
|
requires: []
|
|
@@ -17001,11 +17144,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
17001
17144
|
},
|
|
17002
17145
|
{
|
|
17003
17146
|
name: "scene_create",
|
|
17004
|
-
description: "Creates a scene file, saves one, or saves a copy under a new path. Needs the editor connected.",
|
|
17147
|
+
description: "Creates a scene file where none is, saves one, or saves a copy under a new path. A save of a scene open in the editor saves the editor's copy, unsaved changes included. Needs the editor connected.",
|
|
17005
17148
|
parameters: {
|
|
17006
17149
|
projectPath: PROJECT_PATH,
|
|
17007
17150
|
scenePath: SCENE_PATH,
|
|
17008
|
-
rootNodeType: { type: "string", description: "create: the root node class. Default
|
|
17151
|
+
rootNodeType: { type: "string", description: "create: the root node class. Default Node." },
|
|
17009
17152
|
newPath: { type: "string", description: "save_as: where the copy goes." }
|
|
17010
17153
|
},
|
|
17011
17154
|
requires: ["projectPath", "scenePath"],
|
|
@@ -17029,7 +17172,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17029
17172
|
},
|
|
17030
17173
|
{
|
|
17031
17174
|
name: "scene_node",
|
|
17032
|
-
description: "One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. Needs the editor connected.",
|
|
17175
|
+
description: "One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene, and for a node that belongs to a scene this one instances or inherits. Needs the editor connected.",
|
|
17033
17176
|
parameters: {
|
|
17034
17177
|
projectPath: PROJECT_PATH,
|
|
17035
17178
|
scenePath: SCENE_PATH,
|
|
@@ -17047,7 +17190,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
17047
17190
|
type: "boolean",
|
|
17048
17191
|
description: "get: include properties still at their default. Default false."
|
|
17049
17192
|
},
|
|
17050
|
-
layer: {
|
|
17193
|
+
layer: {
|
|
17194
|
+
type: "number",
|
|
17195
|
+
description: "set_tilemap_cells: the layer of a TileMap. Default 0. A TileMapLayer is one layer."
|
|
17196
|
+
},
|
|
17051
17197
|
cells: TILEMAP_CELLS
|
|
17052
17198
|
},
|
|
17053
17199
|
requires: ["projectPath", "scenePath"],
|
|
@@ -17058,7 +17204,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
17058
17204
|
duplicate: { summary: "copy a node and its children", requires: ["nodePath", "newName"] },
|
|
17059
17205
|
reparent: { summary: "move a node under another parent", requires: ["nodePath", "newParentPath"] },
|
|
17060
17206
|
delete: { summary: "remove a node and its children", requires: ["nodePath"] },
|
|
17061
|
-
set_tilemap_cells: {
|
|
17207
|
+
set_tilemap_cells: {
|
|
17208
|
+
summary: "place tiles in a TileMapLayer or TileMap",
|
|
17209
|
+
requires: ["nodePath", "cells"]
|
|
17210
|
+
}
|
|
17062
17211
|
}
|
|
17063
17212
|
},
|
|
17064
17213
|
{
|
|
@@ -17146,13 +17295,16 @@ var init_tool_definitions = __esm(() => {
|
|
|
17146
17295
|
},
|
|
17147
17296
|
{
|
|
17148
17297
|
name: "script_edit",
|
|
17149
|
-
description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type.
|
|
17298
|
+
description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written.",
|
|
17150
17299
|
parameters: {
|
|
17151
17300
|
projectPath: PROJECT_PATH,
|
|
17152
17301
|
scriptPath: SCRIPT_PATH,
|
|
17153
|
-
className: { type: "string", description: "create: a class_name for the script." },
|
|
17154
|
-
extends: { type: "string", description: "create: the base class. Default Node." },
|
|
17155
|
-
content: {
|
|
17302
|
+
className: { type: "string", description: "create: a class_name for the script. Not with content." },
|
|
17303
|
+
extends: { type: "string", description: "create: the base class. Default Node. Not with content." },
|
|
17304
|
+
content: {
|
|
17305
|
+
type: "string",
|
|
17306
|
+
description: "create: the whole file, written as given, instead of a template, className and extends."
|
|
17307
|
+
},
|
|
17156
17308
|
template: {
|
|
17157
17309
|
type: "string",
|
|
17158
17310
|
enum: ["singleton", "state_machine", "component", "resource"],
|
|
@@ -17350,7 +17502,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17350
17502
|
},
|
|
17351
17503
|
{
|
|
17352
17504
|
name: "editor_output",
|
|
17353
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
17505
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
17354
17506
|
parameters: {
|
|
17355
17507
|
severity: {
|
|
17356
17508
|
type: "string",
|
|
@@ -17414,7 +17566,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17414
17566
|
},
|
|
17415
17567
|
reloadScript: {
|
|
17416
17568
|
type: "string",
|
|
17417
|
-
description: "A script to recompile in the editor after the scan, for the half a scan does not reach: a script the editor has loaded keeps the copy it built, that copy is refreshed when one of its dependencies changes rather than when it changes itself, and a scan leaves it alone. Measured here, a built copy comes back without a method added to it while script_diagnostics on the same script reads clean in the same window, so the copy the editor built and whatever the analyser resolves types against are not known to be one thing: reload for the copy, and read the diagnostics as a separate answer rather than as this one confirmed. This recompiles into the same object, so the holders that kept the stale copy alive see the new one. The methods it has afterwards come back under reloadedMethods, because a reload that compiled nothing and answered OK is the failure worth catching, and what it held before under heldBeforeReload. Its constants come back the same way, under heldConstantsBeforeReload and reloadedConstants, with each enum value as Kind.SHORT: an enum member is held as a constant and never shows among the methods. A script that does not compile is not reloaded: reloadProblem gives Godot's error, says whether the copy the editor holds survived, and names the classes the file uses, whose held copies being behind is what failed one before."
|
|
17569
|
+
description: "A script to recompile in the editor after the scan, for the half a scan does not reach: a script the editor has loaded keeps the copy it built, that copy is refreshed when one of its dependencies changes rather than when it changes itself, and a scan leaves it alone. Measured here, a built copy comes back without a method added to it while script_diagnostics on the same script reads clean in the same window, so the copy the editor built and whatever the analyser resolves types against are not known to be one thing: reload for the copy, and read the diagnostics as a separate answer rather than as this one confirmed. This recompiles into the same object, so the holders that kept the stale copy alive see the new one. The methods it has afterwards come back under reloadedMethods, because a reload that compiled nothing and answered OK is the failure worth catching, and what it held before under heldBeforeReload. Its constants come back the same way, under heldConstantsBeforeReload and reloadedConstants, with each enum value as Kind.SHORT: an enum member is held as a constant and never shows among the methods. A script that does not compile is not reloaded: reloadProblem gives Godot's error, says whether the copy the editor holds survived, and names the classes the file uses, whose held copies being behind is what failed one before. A @tool script is not reloaded either, since it runs inside the editor and reloading one from inside a call can take the editor down: reloadProblem says so, and editor_launch restart recompiles it. A path spelled in another case than the file is reloaded under the spelling on disk, which is the one the editor holds its copy under, and that spelling comes back under reloadedAs."
|
|
17418
17570
|
}
|
|
17419
17571
|
},
|
|
17420
17572
|
requires: ["projectPath"]
|
|
@@ -17560,7 +17712,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17560
17712
|
},
|
|
17561
17713
|
{
|
|
17562
17714
|
name: "runtime_invoke",
|
|
17563
|
-
description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
|
|
17715
|
+
description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. Errors and warnings the engine reported while a called method ran come back under errors, the first 20, with errors_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
|
|
17564
17716
|
parameters: {
|
|
17565
17717
|
projectPath: RUNNING_PROJECT_PATH,
|
|
17566
17718
|
pid: RUNNING_PID,
|
|
@@ -17572,12 +17724,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
17572
17724
|
property: {
|
|
17573
17725
|
type: "string",
|
|
17574
17726
|
ops: ["set"],
|
|
17575
|
-
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a method taking no arguments returned. The answer reads back off the same holder, and a write that leaves the property as it was
|
|
17727
|
+
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a method taking no arguments returned. A map keyed by numbers is stepped into by the number, "_game:units:3". The answer reads back off the same holder, and a write that leaves the property as it was is refused and says what it still holds: the engine not taking it and a setter keeping the value look the same, so the refusal names neither. A write of the value the property already holds, at its own precision, as a float given in 64 bits to a property keeping 32, is not refused: it answers unchanged: true.'
|
|
17576
17728
|
},
|
|
17577
17729
|
value: {
|
|
17578
17730
|
ops: ["set"],
|
|
17579
17731
|
blank: true,
|
|
17580
|
-
description: `set: the value, fitted to the property's type. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key.`
|
|
17732
|
+
description: `set: the value, fitted to the property's type. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance, or null, which the text null also means, to empty it. One declared as Variant or untyped takes anything, as the engine does: a value of the kind it holds is fitted to it, an int staying an int, and any other is stored as sent. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key, as is an object of another script than the one the list holds, whether or not that script has a class_name.`
|
|
17581
17733
|
},
|
|
17582
17734
|
method: {
|
|
17583
17735
|
type: "string",
|
|
@@ -17587,7 +17739,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17587
17739
|
args: {
|
|
17588
17740
|
type: "array",
|
|
17589
17741
|
ops: ["call"],
|
|
17590
|
-
description: `call: the arguments, fitted to the method's parameter types. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths.`
|
|
17742
|
+
description: `call: the arguments, fitted to the method's parameter types. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused, and so are too few or too many arguments. The engine names a parameter typed as a script class without class_name by the class the script extends, so an object of that class with another script passes that check and the engine refuses the call: the answer says the method did not run, with the engine's reason. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths.`
|
|
17591
17743
|
},
|
|
17592
17744
|
timeoutMs: {
|
|
17593
17745
|
type: "integer",
|
|
@@ -17665,7 +17817,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17665
17817
|
says: {
|
|
17666
17818
|
type: "string",
|
|
17667
17819
|
ops: ["click"],
|
|
17668
|
-
description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it, and | separates alternatives), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it, is not on screen. The matches are ranked the way a person picks what to press:
|
|
17820
|
+
description: "click: the words on the control to click, matched the way runtime_inspect find matches says (a plain word is contained in the text, a pattern with * or ? is a glob over it, and | separates alternatives), among the controls on screen, and pressed in the same frame it was found in, so a panel rebuilding between a find and a click cannot free it first. Words on a label inside a button press the button. A control with something drawn over its centre that would take the pointer, such as a screen shown over it or a dialog stacked above its own, is not on screen; nor is one on a hidden canvas layer, one outside the viewport, or one a container clips away that a click does not scroll. The refusal counts those apart. The matches are ranked the way a person picks what to press: an enabled button before anything else, a disabled one included, then a control whose whole text, or one whole line of it such as the title on a card, is the words before one saying them as part of more, and the best one is pressed; found.picked says why. Several matches at the best rank are refused with each one listed, and index picks one."
|
|
17669
17821
|
},
|
|
17670
17822
|
action: {
|
|
17671
17823
|
type: "string",
|
|
@@ -17698,7 +17850,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17698
17850
|
replace: {
|
|
17699
17851
|
type: "boolean",
|
|
17700
17852
|
ops: ["text"],
|
|
17701
|
-
description: 'text: true writes over what the field already says, which is what filling one in means. Default false types at the caret, so a field reading 2.1 typed "0.3" at reads 2.10.3. The answer says what the field holds afterwards either way.'
|
|
17853
|
+
description: 'text: true writes over what the field already says, which is what filling one in means. Default false types at the caret, so a field reading 2.1 typed "0.3" at reads 2.10.3. The answer says what the field holds afterwards either way. A read-only field is refused.'
|
|
17702
17854
|
},
|
|
17703
17855
|
index: {
|
|
17704
17856
|
type: "number",
|
|
@@ -17742,11 +17894,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
17742
17894
|
requires: [],
|
|
17743
17895
|
operations: {
|
|
17744
17896
|
click: {
|
|
17745
|
-
summary: "press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. The control is named by nodePath, or by says with the words on it, found under nodePath when both are given, and the answer then says under found which match it was. A control out of sight inside a ScrollContainer is scrolled to first, and scrolled_into_view says whether the
|
|
17897
|
+
summary: "press and release on a Control, a frame apart, and answer with what was under the pointer and what became of the control: in_tree, removed or freed. The control is named by nodePath, or by says with the words on it, found under nodePath when both are given, and the answer then says under found which match it was. A control out of sight inside a ScrollContainer is scrolled to first, each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A disabled button, and a control that does not process (the game paused, say), are refused: the pointer reaches them and the press does nothing. A 3D node is clicked where it is drawn, and landed then says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room",
|
|
17746
17898
|
requires: []
|
|
17747
17899
|
},
|
|
17748
17900
|
choose: {
|
|
17749
|
-
summary: "take an item out of a menu, by what it says or by where it is in the list. A menu's items are drawn rather than built, so there is nothing to click: the item takes the focus and
|
|
17901
|
+
summary: "take an item out of a menu, by what it says or by where it is in the list. A menu's items are drawn rather than built, so there is nothing to click: the menu is opened, the item is looked for in it as it stands then, since a game can fill a menu as it opens, and the item takes the focus and ui_accept presses it, which is the engine's own path, needs no window, and does not depend on what ui_accept is bound to. A disabled or hidden button in front of the menu is refused, and so is a press the menu did not take, which is heard rather than assumed. Answers with what was chosen, read before the press, what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick), and what the button in front of it shows now when it is still there",
|
|
17750
17902
|
requires: ["nodePath"]
|
|
17751
17903
|
},
|
|
17752
17904
|
action: { summary: "press an action, or hold it", requires: ["action"] },
|
|
@@ -17755,7 +17907,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17755
17907
|
requires: ["keycode"]
|
|
17756
17908
|
},
|
|
17757
17909
|
text: {
|
|
17758
|
-
summary: "type a string into the field being edited, a character at a time, and say what it landed in and what it holds afterwards, a secret field as its mask",
|
|
17910
|
+
summary: "type a string into the field being edited, a character at a time, and say what it landed in and what it holds afterwards, a secret field as its mask. The field is the focus of the dialog that has the focus when one does, which is where the keys go",
|
|
17759
17911
|
requires: ["text"]
|
|
17760
17912
|
},
|
|
17761
17913
|
mouse_click: {
|
|
@@ -17843,7 +17995,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17843
17995
|
},
|
|
17844
17996
|
{
|
|
17845
17997
|
name: "debug_control",
|
|
17846
|
-
description: "Continues or steps the debugged game through the editor's debug adapter
|
|
17998
|
+
description: "Continues or steps the debugged game through the editor's debug adapter. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. There is no pause and no step_out: Godot's adapter answers a pause by reporting the game stopped and leaving it running, and implements no stepOut at all, so hold the game where you want it with a breakpoint and step over or into from there.",
|
|
17847
17999
|
parameters: {},
|
|
17848
18000
|
requires: [],
|
|
17849
18001
|
operations: {
|
|
@@ -17854,12 +18006,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
17854
18006
|
},
|
|
17855
18007
|
{
|
|
17856
18008
|
name: "debug_state",
|
|
17857
|
-
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debug adapter's console output so far. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack.",
|
|
18009
|
+
description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debug adapter's console output so far, its last thousand lines, with any earlier ones counted under notShown. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack.",
|
|
17858
18010
|
parameters: {
|
|
17859
18011
|
frameId: {
|
|
17860
18012
|
type: "number",
|
|
17861
18013
|
ops: ["variables"],
|
|
17862
|
-
description: "variables: which frame, from a stack answer. Default the innermost."
|
|
18014
|
+
description: "variables: which frame, from a stack answer. Default the innermost. A frame the stack no longer holds is refused, and a scope whose values did not arrive in time answers variables null and is named under valuesMissing."
|
|
17863
18015
|
}
|
|
17864
18016
|
},
|
|
17865
18017
|
requires: [],
|
|
@@ -32626,10 +32778,10 @@ var init_stdio2 = __esm(() => {
|
|
|
32626
32778
|
});
|
|
32627
32779
|
|
|
32628
32780
|
// src/runtime-client.ts
|
|
32629
|
-
import { existsSync as
|
|
32781
|
+
import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync5, unlinkSync } from "node:fs";
|
|
32630
32782
|
import { createConnection } from "node:net";
|
|
32631
32783
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
32632
|
-
import { dirname as
|
|
32784
|
+
import { dirname as dirname8, join as join13, resolve as resolve3 } from "node:path";
|
|
32633
32785
|
import { setTimeout as delay } from "node:timers/promises";
|
|
32634
32786
|
function runtimeDirectory(variables = process.env) {
|
|
32635
32787
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -32650,7 +32802,7 @@ function runtimeDirectories(variables = process.env) {
|
|
|
32650
32802
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
32651
32803
|
for (const directory of directories) {
|
|
32652
32804
|
const path = join13(directory, `runtime-${pid}.log`);
|
|
32653
|
-
if (
|
|
32805
|
+
if (existsSync12(path)) {
|
|
32654
32806
|
return path;
|
|
32655
32807
|
}
|
|
32656
32808
|
}
|
|
@@ -32681,7 +32833,7 @@ function reportAlreadyInTranscript(transcript, report) {
|
|
|
32681
32833
|
function parseAnnouncement(file, pid) {
|
|
32682
32834
|
let fields;
|
|
32683
32835
|
try {
|
|
32684
|
-
fields = asParams(JSON.parse(
|
|
32836
|
+
fields = asParams(JSON.parse(readFileSync11(file, "utf8")));
|
|
32685
32837
|
} catch {
|
|
32686
32838
|
return { kind: "rubbish" };
|
|
32687
32839
|
}
|
|
@@ -32739,7 +32891,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
|
|
|
32739
32891
|
function wentAway(file, pid) {
|
|
32740
32892
|
let project = "";
|
|
32741
32893
|
try {
|
|
32742
|
-
const fields = asParams(JSON.parse(
|
|
32894
|
+
const fields = asParams(JSON.parse(readFileSync11(file, "utf8")));
|
|
32743
32895
|
project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
|
|
32744
32896
|
} catch {
|
|
32745
32897
|
return;
|
|
@@ -32782,7 +32934,7 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
|
|
|
32782
32934
|
}).map((one) => one.file));
|
|
32783
32935
|
}
|
|
32784
32936
|
function announcedIn(directory) {
|
|
32785
|
-
if (!
|
|
32937
|
+
if (!existsSync12(directory)) {
|
|
32786
32938
|
return [];
|
|
32787
32939
|
}
|
|
32788
32940
|
const found = [];
|
|
@@ -32828,14 +32980,14 @@ function announcedIn(directory) {
|
|
|
32828
32980
|
const held = new Set(entries.map((one) => one.file));
|
|
32829
32981
|
const here = join13(directory, ".");
|
|
32830
32982
|
for (const file of verdicts.keys()) {
|
|
32831
|
-
if (
|
|
32983
|
+
if (dirname8(file) === here && !held.has(file)) {
|
|
32832
32984
|
verdicts.delete(file);
|
|
32833
32985
|
}
|
|
32834
32986
|
}
|
|
32835
32987
|
return found;
|
|
32836
32988
|
}
|
|
32837
32989
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
32838
|
-
const files = directories.map((directory) => join13(directory, `runtime-${pid}.json`)).filter((file) =>
|
|
32990
|
+
const files = directories.map((directory) => join13(directory, `runtime-${pid}.json`)).filter((file) => existsSync12(file));
|
|
32839
32991
|
const first = files[0];
|
|
32840
32992
|
if (first === undefined) {
|
|
32841
32993
|
return;
|
|
@@ -32870,13 +33022,13 @@ async function announcedSince(projectPath, before, waiting = {}) {
|
|
|
32870
33022
|
const directories = waiting.directories ?? runtimeDirectories();
|
|
32871
33023
|
const until = Date.now() + Math.max(waiting.budgetMs ?? ANNOUNCE_BUDGET_MS, 0);
|
|
32872
33024
|
for (;; ) {
|
|
32873
|
-
const fresh = discoverRuntimes(directories).
|
|
32874
|
-
if (fresh
|
|
33025
|
+
const fresh = discoverRuntimes(directories).filter((endpoint) => !before.has(endpoint.pid) && isSameDirectory(endpoint.project.path, projectPath) && (waiting.accept?.(endpoint) ?? true));
|
|
33026
|
+
if (fresh.length > 0) {
|
|
32875
33027
|
return fresh;
|
|
32876
33028
|
}
|
|
32877
33029
|
const left = until - Date.now();
|
|
32878
33030
|
if (left <= 0 || waiting.giveUp?.() === true) {
|
|
32879
|
-
return
|
|
33031
|
+
return [];
|
|
32880
33032
|
}
|
|
32881
33033
|
await delay(Math.min(LOOK_EVERY_MS, left));
|
|
32882
33034
|
}
|
|
@@ -33135,14 +33287,14 @@ var init_runtime_client = __esm(() => {
|
|
|
33135
33287
|
});
|
|
33136
33288
|
|
|
33137
33289
|
// src/boot-note.ts
|
|
33138
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
33139
|
-
import { dirname as
|
|
33290
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
|
|
33291
|
+
import { dirname as dirname9, join as join14 } from "node:path";
|
|
33140
33292
|
function bootNotePath(projectPath) {
|
|
33141
33293
|
return join14(projectPath, ".godot", "gdharness-boot.json");
|
|
33142
33294
|
}
|
|
33143
33295
|
function readBootNote(projectPath) {
|
|
33144
33296
|
try {
|
|
33145
|
-
const noted = readNumber(asParams(JSON.parse(
|
|
33297
|
+
const noted = readNumber(asParams(JSON.parse(readFileSync12(bootNotePath(projectPath), "utf8"))), "announcedAfterMs");
|
|
33146
33298
|
return noted !== undefined && Number.isFinite(noted) && noted > 0 ? noted : null;
|
|
33147
33299
|
} catch {
|
|
33148
33300
|
return null;
|
|
@@ -33151,7 +33303,7 @@ function readBootNote(projectPath) {
|
|
|
33151
33303
|
function writeBootNote(projectPath, announcedAfterMs) {
|
|
33152
33304
|
try {
|
|
33153
33305
|
const path = bootNotePath(projectPath);
|
|
33154
|
-
mkdirSync6(
|
|
33306
|
+
mkdirSync6(dirname9(path), { recursive: true });
|
|
33155
33307
|
writeFileSync8(path, JSON.stringify({ announcedAfterMs: Math.round(announcedAfterMs), at: new Date().toISOString() }, null, 2), "utf8");
|
|
33156
33308
|
} catch {}
|
|
33157
33309
|
}
|
|
@@ -33170,8 +33322,8 @@ var init_boot_note = __esm(() => {
|
|
|
33170
33322
|
});
|
|
33171
33323
|
|
|
33172
33324
|
// src/breakpoint-note.ts
|
|
33173
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
33174
|
-
import { dirname as
|
|
33325
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
33326
|
+
import { dirname as dirname10, isAbsolute as isAbsolute2, join as join15, relative as relative2 } from "node:path";
|
|
33175
33327
|
function breakpointNotePath(projectPath) {
|
|
33176
33328
|
return join15(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
33177
33329
|
}
|
|
@@ -33190,7 +33342,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
33190
33342
|
lines: [...one.lines]
|
|
33191
33343
|
}))
|
|
33192
33344
|
};
|
|
33193
|
-
mkdirSync7(
|
|
33345
|
+
mkdirSync7(dirname10(path), { recursive: true });
|
|
33194
33346
|
writeFileSync9(path, `${JSON.stringify(note, null, 2)}
|
|
33195
33347
|
`, "utf8");
|
|
33196
33348
|
} catch (error) {
|
|
@@ -33199,7 +33351,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
33199
33351
|
}
|
|
33200
33352
|
function readBreakpointNote(projectPath) {
|
|
33201
33353
|
try {
|
|
33202
|
-
const parsed = JSON.parse(
|
|
33354
|
+
const parsed = JSON.parse(readFileSync13(breakpointNotePath(projectPath), "utf8"));
|
|
33203
33355
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
|
|
33204
33356
|
return [];
|
|
33205
33357
|
}
|
|
@@ -33232,14 +33384,14 @@ var init_breakpoint_note = __esm(() => {
|
|
|
33232
33384
|
});
|
|
33233
33385
|
|
|
33234
33386
|
// src/class-note.ts
|
|
33235
|
-
import { mkdirSync as mkdirSync8, readFileSync as
|
|
33236
|
-
import { dirname as
|
|
33387
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
|
|
33388
|
+
import { dirname as dirname11, join as join16 } from "node:path";
|
|
33237
33389
|
function classNotePath(projectPath) {
|
|
33238
33390
|
return join16(projectPath, ".godot", "gdharness-classes.json");
|
|
33239
33391
|
}
|
|
33240
33392
|
function readClassNote(projectPath) {
|
|
33241
33393
|
try {
|
|
33242
|
-
const fields = asParams(JSON.parse(
|
|
33394
|
+
const fields = asParams(JSON.parse(readFileSync14(classNotePath(projectPath), "utf8")));
|
|
33243
33395
|
const rebuiltWith = readArray(fields, "rebuiltWith");
|
|
33244
33396
|
if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
|
|
33245
33397
|
return null;
|
|
@@ -33256,7 +33408,7 @@ function readClassNote(projectPath) {
|
|
|
33256
33408
|
function writeClassNote(projectPath, note) {
|
|
33257
33409
|
try {
|
|
33258
33410
|
const path = classNotePath(projectPath);
|
|
33259
|
-
mkdirSync8(
|
|
33411
|
+
mkdirSync8(dirname11(path), { recursive: true });
|
|
33260
33412
|
writeFileSync10(path, `${JSON.stringify(note, null, 2)}
|
|
33261
33413
|
`, "utf8");
|
|
33262
33414
|
} catch {}
|
|
@@ -33264,16 +33416,16 @@ function writeClassNote(projectPath, note) {
|
|
|
33264
33416
|
var init_class_note = () => {};
|
|
33265
33417
|
|
|
33266
33418
|
// src/config-pin.ts
|
|
33267
|
-
import { existsSync as
|
|
33419
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
|
|
33268
33420
|
import { join as join17 } from "node:path";
|
|
33269
33421
|
function versionsNamedInConfig(projectPath) {
|
|
33270
33422
|
const config = join17(projectPath, ".mcp.json");
|
|
33271
|
-
if (!
|
|
33423
|
+
if (!existsSync13(config)) {
|
|
33272
33424
|
return [];
|
|
33273
33425
|
}
|
|
33274
33426
|
let text;
|
|
33275
33427
|
try {
|
|
33276
|
-
text =
|
|
33428
|
+
text = readFileSync15(config, "utf8");
|
|
33277
33429
|
JSON.parse(text);
|
|
33278
33430
|
} catch {
|
|
33279
33431
|
return [];
|
|
@@ -33422,6 +33574,9 @@ var init_ports = __esm(() => {
|
|
|
33422
33574
|
// src/dap_client.ts
|
|
33423
33575
|
import { createConnection as createConnection2 } from "node:net";
|
|
33424
33576
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
33577
|
+
function fileKey(path) {
|
|
33578
|
+
return process.platform === "win32" ? path.replaceAll("\\", "/").toLowerCase() : path;
|
|
33579
|
+
}
|
|
33425
33580
|
function said(body, field) {
|
|
33426
33581
|
const value = body?.[field];
|
|
33427
33582
|
return typeof value === "string" ? value : "";
|
|
@@ -33438,17 +33593,25 @@ class GodotDAPClient {
|
|
|
33438
33593
|
outputBuffer = [];
|
|
33439
33594
|
onOutput = null;
|
|
33440
33595
|
maxOutputLines = 1000;
|
|
33596
|
+
outputDropped = 0;
|
|
33597
|
+
outputTaken = 0;
|
|
33441
33598
|
initialized = false;
|
|
33442
33599
|
attached = false;
|
|
33443
33600
|
lastThreadId = 1;
|
|
33444
33601
|
halt = null;
|
|
33445
33602
|
holdKnown = false;
|
|
33603
|
+
stops = 0;
|
|
33446
33604
|
breakpoints = new Map;
|
|
33605
|
+
spelled = new Map;
|
|
33447
33606
|
inEditor = new Map;
|
|
33448
33607
|
onBreakpointsChanged = null;
|
|
33449
|
-
|
|
33608
|
+
scopesPatienceMs;
|
|
33609
|
+
stepPatienceMs;
|
|
33610
|
+
constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1", patience = {}) {
|
|
33450
33611
|
this.port = port;
|
|
33451
33612
|
this.host = host;
|
|
33613
|
+
this.scopesPatienceMs = patience.scopesMs ?? SCOPES_TIMEOUT_MS;
|
|
33614
|
+
this.stepPatienceMs = patience.stepMs ?? STEP_LANDS_WITHIN_MS;
|
|
33452
33615
|
this.pendingRequests = new Map;
|
|
33453
33616
|
}
|
|
33454
33617
|
async connect() {
|
|
@@ -33609,6 +33772,7 @@ class GodotDAPClient {
|
|
|
33609
33772
|
}
|
|
33610
33773
|
}
|
|
33611
33774
|
if (this.outputBuffer.length > this.maxOutputLines) {
|
|
33775
|
+
this.outputDropped += this.outputBuffer.length - this.maxOutputLines;
|
|
33612
33776
|
this.outputBuffer = this.outputBuffer.slice(this.outputBuffer.length - this.maxOutputLines);
|
|
33613
33777
|
}
|
|
33614
33778
|
return;
|
|
@@ -33625,6 +33789,7 @@ class GodotDAPClient {
|
|
|
33625
33789
|
text: said(body, "text")
|
|
33626
33790
|
};
|
|
33627
33791
|
this.holdKnown = true;
|
|
33792
|
+
this.stops += 1;
|
|
33628
33793
|
return;
|
|
33629
33794
|
}
|
|
33630
33795
|
if (eventName === "continued") {
|
|
@@ -33641,20 +33806,24 @@ class GodotDAPClient {
|
|
|
33641
33806
|
if (typeof path !== "string" || typeof line !== "number") {
|
|
33642
33807
|
return;
|
|
33643
33808
|
}
|
|
33809
|
+
const key = fileKey(path);
|
|
33810
|
+
if (!this.spelled.has(key)) {
|
|
33811
|
+
this.spelled.set(key, path);
|
|
33812
|
+
}
|
|
33644
33813
|
if (said(body, "reason") === "removed") {
|
|
33645
|
-
this.inEditor.get(
|
|
33646
|
-
const held = this.breakpoints.get(
|
|
33814
|
+
this.inEditor.get(key)?.delete(line);
|
|
33815
|
+
const held = this.breakpoints.get(key);
|
|
33647
33816
|
if (held?.delete(line) === true) {
|
|
33648
33817
|
if (held.size === 0) {
|
|
33649
|
-
this.breakpoints.delete(
|
|
33818
|
+
this.breakpoints.delete(key);
|
|
33650
33819
|
}
|
|
33651
33820
|
this.onBreakpointsChanged?.(this.breakpointsHeld());
|
|
33652
33821
|
}
|
|
33653
33822
|
return;
|
|
33654
33823
|
}
|
|
33655
|
-
const known = this.inEditor.get(
|
|
33824
|
+
const known = this.inEditor.get(key) ?? new Set;
|
|
33656
33825
|
known.add(line);
|
|
33657
|
-
this.inEditor.set(
|
|
33826
|
+
this.inEditor.set(key, known);
|
|
33658
33827
|
return;
|
|
33659
33828
|
}
|
|
33660
33829
|
if (eventName === "terminated" || eventName === "exited") {
|
|
@@ -33737,24 +33906,44 @@ class GodotDAPClient {
|
|
|
33737
33906
|
getOutput(clear = false) {
|
|
33738
33907
|
const lines = [...this.outputBuffer];
|
|
33739
33908
|
if (clear) {
|
|
33909
|
+
this.outputTaken += lines.length;
|
|
33740
33910
|
this.outputBuffer = [];
|
|
33741
33911
|
}
|
|
33742
33912
|
return lines;
|
|
33743
33913
|
}
|
|
33914
|
+
outputSoFar() {
|
|
33915
|
+
const output = this.getOutput(false);
|
|
33916
|
+
const notShown = this.outputDropped + this.outputTaken;
|
|
33917
|
+
if (notShown === 0) {
|
|
33918
|
+
return { lines: output.length, output };
|
|
33919
|
+
}
|
|
33920
|
+
const where = [
|
|
33921
|
+
this.outputDropped > 0 ? `${this.outputDropped} fell out of this buffer, which keeps the last ${this.maxOutputLines}` : "",
|
|
33922
|
+
this.outputTaken > 0 ? `${this.outputTaken} were moved into the run's log by an earlier read` : ""
|
|
33923
|
+
].filter((part) => part !== "");
|
|
33924
|
+
return {
|
|
33925
|
+
lines: output.length,
|
|
33926
|
+
output,
|
|
33927
|
+
notShown,
|
|
33928
|
+
note: `${notShown} earlier ${notShown === 1 ? "line is" : "lines are"} not in this answer: ${where.join(", and ")}. editor_output reads what the server kept of the run's console.`
|
|
33929
|
+
};
|
|
33930
|
+
}
|
|
33744
33931
|
async setBreakpoint(filePath, line) {
|
|
33745
|
-
const wanted = new Set(this.breakpoints.get(filePath) ?? []);
|
|
33932
|
+
const wanted = new Set(this.breakpoints.get(fileKey(filePath)) ?? []);
|
|
33746
33933
|
wanted.add(line);
|
|
33747
33934
|
return await this.sendBreakpoints(filePath, wanted);
|
|
33748
33935
|
}
|
|
33749
33936
|
async removeBreakpoint(filePath, line) {
|
|
33750
|
-
const wanted = new Set(this.breakpoints.get(filePath) ?? []);
|
|
33937
|
+
const wanted = new Set(this.breakpoints.get(fileKey(filePath)) ?? []);
|
|
33751
33938
|
wanted.delete(line);
|
|
33752
33939
|
return await this.sendBreakpoints(filePath, wanted, line);
|
|
33753
33940
|
}
|
|
33754
33941
|
async sendBreakpoints(filePath, mine, clearing) {
|
|
33755
33942
|
await this.ensureConnected();
|
|
33756
33943
|
await this.initialize();
|
|
33757
|
-
const
|
|
33944
|
+
const key = fileKey(filePath);
|
|
33945
|
+
this.spelled.set(key, filePath);
|
|
33946
|
+
const lines = new Set([...mine, ...this.inEditor.get(key) ?? []]);
|
|
33758
33947
|
if (clearing !== undefined) {
|
|
33759
33948
|
lines.delete(clearing);
|
|
33760
33949
|
}
|
|
@@ -33764,14 +33953,14 @@ class GodotDAPClient {
|
|
|
33764
33953
|
breakpoints: sorted.map((breakpointLine) => ({ line: breakpointLine }))
|
|
33765
33954
|
});
|
|
33766
33955
|
if (mine.size === 0) {
|
|
33767
|
-
this.breakpoints.delete(
|
|
33956
|
+
this.breakpoints.delete(key);
|
|
33768
33957
|
} else {
|
|
33769
|
-
this.breakpoints.set(
|
|
33958
|
+
this.breakpoints.set(key, new Set(mine));
|
|
33770
33959
|
}
|
|
33771
33960
|
if (lines.size === 0) {
|
|
33772
|
-
this.inEditor.delete(
|
|
33961
|
+
this.inEditor.delete(key);
|
|
33773
33962
|
} else {
|
|
33774
|
-
this.inEditor.set(
|
|
33963
|
+
this.inEditor.set(key, lines);
|
|
33775
33964
|
}
|
|
33776
33965
|
return answer;
|
|
33777
33966
|
}
|
|
@@ -33780,11 +33969,11 @@ class GodotDAPClient {
|
|
|
33780
33969
|
}
|
|
33781
33970
|
breakpointsInEditor() {
|
|
33782
33971
|
const theirs = [];
|
|
33783
|
-
for (const [
|
|
33784
|
-
const mine = this.breakpoints.get(
|
|
33972
|
+
for (const [key, lines] of this.inEditor) {
|
|
33973
|
+
const mine = this.breakpoints.get(key);
|
|
33785
33974
|
const rest = Array.from(lines).filter((line) => !mine?.has(line)).sort((a, b) => a - b);
|
|
33786
33975
|
if (rest.length > 0) {
|
|
33787
|
-
theirs.push({ scriptPath, lines: rest });
|
|
33976
|
+
theirs.push({ scriptPath: this.spelled.get(key) ?? key, lines: rest });
|
|
33788
33977
|
}
|
|
33789
33978
|
}
|
|
33790
33979
|
return theirs;
|
|
@@ -33792,12 +33981,13 @@ class GodotDAPClient {
|
|
|
33792
33981
|
async reapplyBreakpoints() {
|
|
33793
33982
|
const applied = [];
|
|
33794
33983
|
const refused = [];
|
|
33795
|
-
for (const [
|
|
33984
|
+
for (const [key, lines] of Array.from(this.breakpoints)) {
|
|
33985
|
+
const filePath = this.spelled.get(key) ?? key;
|
|
33796
33986
|
try {
|
|
33797
33987
|
await this.sendBreakpoints(filePath, lines);
|
|
33798
33988
|
applied.push({ scriptPath: filePath, lines: Array.from(lines).sort((a, b) => a - b) });
|
|
33799
33989
|
} catch (error) {
|
|
33800
|
-
this.breakpoints.delete(
|
|
33990
|
+
this.breakpoints.delete(key);
|
|
33801
33991
|
refused.push({
|
|
33802
33992
|
scriptPath: filePath,
|
|
33803
33993
|
reason: error instanceof Error ? error.message : String(error)
|
|
@@ -33810,19 +34000,21 @@ class GodotDAPClient {
|
|
|
33810
34000
|
return { applied, refused };
|
|
33811
34001
|
}
|
|
33812
34002
|
breakpointsHeld() {
|
|
33813
|
-
return Array.from(this.breakpoints, ([
|
|
33814
|
-
scriptPath,
|
|
34003
|
+
return Array.from(this.breakpoints, ([key, lines]) => ({
|
|
34004
|
+
scriptPath: this.spelled.get(key) ?? key,
|
|
33815
34005
|
lines: Array.from(lines).sort((a, b) => a - b)
|
|
33816
34006
|
}));
|
|
33817
34007
|
}
|
|
33818
34008
|
holdBreakpoints(held) {
|
|
33819
34009
|
for (const { scriptPath, lines } of held) {
|
|
33820
|
-
const
|
|
34010
|
+
const key = fileKey(scriptPath);
|
|
34011
|
+
const fileBreakpoints = this.breakpoints.get(key) ?? new Set;
|
|
33821
34012
|
for (const line of lines) {
|
|
33822
34013
|
fileBreakpoints.add(line);
|
|
33823
34014
|
}
|
|
33824
34015
|
if (fileBreakpoints.size > 0) {
|
|
33825
|
-
this.breakpoints.set(
|
|
34016
|
+
this.breakpoints.set(key, fileBreakpoints);
|
|
34017
|
+
this.spelled.set(key, scriptPath);
|
|
33826
34018
|
}
|
|
33827
34019
|
}
|
|
33828
34020
|
}
|
|
@@ -33833,15 +34025,16 @@ class GodotDAPClient {
|
|
|
33833
34025
|
this.halt = null;
|
|
33834
34026
|
this.holdKnown = true;
|
|
33835
34027
|
}
|
|
33836
|
-
async
|
|
33837
|
-
await this.attach();
|
|
33838
|
-
const resolvedThreadId = await this.resolveThreadId(threadId);
|
|
33839
|
-
await this.sendRequest("next", { threadId: resolvedThreadId });
|
|
33840
|
-
}
|
|
33841
|
-
async stepInto(threadId) {
|
|
34028
|
+
async step(command) {
|
|
33842
34029
|
await this.attach();
|
|
33843
|
-
const resolvedThreadId = await this.resolveThreadId(
|
|
33844
|
-
|
|
34030
|
+
const resolvedThreadId = await this.resolveThreadId();
|
|
34031
|
+
const before = this.stops;
|
|
34032
|
+
await this.sendRequest(command, { threadId: resolvedThreadId });
|
|
34033
|
+
const deadline = Date.now() + this.stepPatienceMs;
|
|
34034
|
+
while (this.stops === before && this.connected && Date.now() < deadline) {
|
|
34035
|
+
await delay2(50);
|
|
34036
|
+
}
|
|
34037
|
+
return this.stops !== before;
|
|
33845
34038
|
}
|
|
33846
34039
|
async getStackTrace(threadId) {
|
|
33847
34040
|
await this.attach();
|
|
@@ -33867,16 +34060,16 @@ class GodotDAPClient {
|
|
|
33867
34060
|
} catch {}
|
|
33868
34061
|
await delay2(200);
|
|
33869
34062
|
}
|
|
33870
|
-
return
|
|
34063
|
+
return null;
|
|
33871
34064
|
}
|
|
33872
34065
|
async getScopes(frameId) {
|
|
33873
34066
|
await this.attach();
|
|
33874
34067
|
const frames = await this.getStackTrace();
|
|
33875
34068
|
const frame = frameId === undefined ? frames[0] : frames.find((each) => each["id"] === frameId);
|
|
33876
34069
|
if (frame === undefined) {
|
|
33877
|
-
|
|
34070
|
+
throw new ReadRefusal(frames.length === 0 ? "The adapter gave no stack, so there is no frame to read variables from" : `The stack the adapter gives now has no frame ${String(frameId)}; its frames are ${frames.map((each) => String(each["id"])).join(", ")}. Read the stack again for the frames the game is at now`);
|
|
33878
34071
|
}
|
|
33879
|
-
const deadline = Date.now() +
|
|
34072
|
+
const deadline = Date.now() + this.scopesPatienceMs;
|
|
33880
34073
|
let scopes = [];
|
|
33881
34074
|
while (scopes.length === 0 && Date.now() < deadline) {
|
|
33882
34075
|
try {
|
|
@@ -33888,6 +34081,9 @@ class GodotDAPClient {
|
|
|
33888
34081
|
} catch {}
|
|
33889
34082
|
await delay2(200);
|
|
33890
34083
|
}
|
|
34084
|
+
if (scopes.length === 0) {
|
|
34085
|
+
throw new ReadRefusal(`The scopes of frame ${String(frame["id"])} did not arrive from the game within ${this.scopesPatienceMs / 1000}s. They come from the game through the editor, so a busy machine can take longer: ask again`);
|
|
34086
|
+
}
|
|
33891
34087
|
const named = [];
|
|
33892
34088
|
for (const scope of scopes) {
|
|
33893
34089
|
const reference = scope["variablesReference"];
|
|
@@ -33951,9 +34147,8 @@ async function handleDAPTool(client, toolName, args) {
|
|
|
33951
34147
|
try {
|
|
33952
34148
|
switch (toolName) {
|
|
33953
34149
|
case "dap_get_output": {
|
|
33954
|
-
const output = client.getOutput(false);
|
|
33955
34150
|
return {
|
|
33956
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
34151
|
+
content: [{ type: "text", text: JSON.stringify(client.outputSoFar(), null, 2) }]
|
|
33957
34152
|
};
|
|
33958
34153
|
}
|
|
33959
34154
|
case "dap_set_breakpoint": {
|
|
@@ -33980,19 +34175,13 @@ async function handleDAPTool(client, toolName, args) {
|
|
|
33980
34175
|
}
|
|
33981
34176
|
case "dap_step_over":
|
|
33982
34177
|
case "dap_step_into": {
|
|
33983
|
-
|
|
33984
|
-
|
|
33985
|
-
|
|
33986
|
-
|
|
33987
|
-
|
|
33988
|
-
return {
|
|
33989
|
-
content: [
|
|
33990
|
-
{
|
|
33991
|
-
type: "text",
|
|
33992
|
-
text: JSON.stringify({ stepped: true, stack: await client.getStackTrace() }, null, 2)
|
|
33993
|
-
}
|
|
33994
|
-
]
|
|
34178
|
+
const heldAgain = await client.step(toolName === "dap_step_into" ? "stepIn" : "next");
|
|
34179
|
+
const answer = heldAgain ? { stepped: true, heldAgain, stack: await client.getStackTrace() } : {
|
|
34180
|
+
stepped: true,
|
|
34181
|
+
heldAgain,
|
|
34182
|
+
note: `The game had not stopped again ${client.stepPatienceMs / 1000}s after the step: the step is still running, it went on without stopping anywhere, or the game was not held when it was asked. debug_state stack answers once it stops.`
|
|
33995
34183
|
};
|
|
34184
|
+
return { content: [{ type: "text", text: JSON.stringify(answer, null, 2) }] };
|
|
33996
34185
|
}
|
|
33997
34186
|
case "dap_get_stack_trace": {
|
|
33998
34187
|
const stack = await client.getStackTrace();
|
|
@@ -34003,8 +34192,14 @@ async function handleDAPTool(client, toolName, args) {
|
|
|
34003
34192
|
case "dap_get_variables": {
|
|
34004
34193
|
const frameId = typeof safeArgs.frameId === "number" ? safeArgs.frameId : undefined;
|
|
34005
34194
|
const scopes = await client.getScopes(frameId);
|
|
34195
|
+
const missing = scopes.filter((scope) => scope.variables === null).map((scope) => scope.name);
|
|
34196
|
+
const answer = missing.length === 0 ? { scopes } : {
|
|
34197
|
+
scopes,
|
|
34198
|
+
valuesMissing: missing,
|
|
34199
|
+
note: `The values of ${missing.join(", ")} did not arrive from the game within ${client.scopesPatienceMs / 1000}s, so they are unknown rather than empty. Ask again.`
|
|
34200
|
+
};
|
|
34006
34201
|
return {
|
|
34007
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
34202
|
+
content: [{ type: "text", text: JSON.stringify(answer, null, 2) }]
|
|
34008
34203
|
};
|
|
34009
34204
|
}
|
|
34010
34205
|
default:
|
|
@@ -34012,18 +34207,22 @@ async function handleDAPTool(client, toolName, args) {
|
|
|
34012
34207
|
}
|
|
34013
34208
|
} catch (error) {
|
|
34014
34209
|
const message = error instanceof Error ? error.message : String(error);
|
|
34015
|
-
const cure = toolName === "dap_set_breakpoint" || toolName === "dap_remove_breakpoint" ? "Breakpoints need the editor open, not a running game: editor_status says whether one is connected, and the path is the script as the project spells it." : "The debug tools answer for a game the editor is playing: start one with editor_run.";
|
|
34210
|
+
const cure = error instanceof ReadRefusal ? "" : toolName === "dap_set_breakpoint" || toolName === "dap_remove_breakpoint" ? "Breakpoints need the editor open, not a running game: editor_status says whether one is connected, and the path is the script as the project spells it." : "The debug tools answer for a game the editor is playing: start one with editor_run.";
|
|
34016
34211
|
return {
|
|
34017
34212
|
isError: true,
|
|
34018
|
-
content: [
|
|
34213
|
+
content: [
|
|
34214
|
+
{ type: "text", text: `DAP tool '${toolName}' failed: ${message}.${cure === "" ? "" : ` ${cure}`}` }
|
|
34215
|
+
]
|
|
34019
34216
|
};
|
|
34020
34217
|
}
|
|
34021
34218
|
}
|
|
34022
|
-
var DEFAULT_DAP_PORT = 6006, DAP_REQUEST_TIMEOUT_MS = 1e4, SCOPES_TIMEOUT_MS = 20000, VARIABLES_POLL_TIMEOUT_MS = 2000;
|
|
34219
|
+
var DEFAULT_DAP_PORT = 6006, DAP_REQUEST_TIMEOUT_MS = 1e4, SCOPES_TIMEOUT_MS = 20000, STEP_LANDS_WITHIN_MS = 1e4, VARIABLES_POLL_TIMEOUT_MS = 2000, ReadRefusal;
|
|
34023
34220
|
var init_dap_client = __esm(() => {
|
|
34024
34221
|
init_errors();
|
|
34025
34222
|
init_framing();
|
|
34026
34223
|
init_ports();
|
|
34224
|
+
ReadRefusal = class ReadRefusal extends Refusal {
|
|
34225
|
+
};
|
|
34027
34226
|
});
|
|
34028
34227
|
|
|
34029
34228
|
// src/desktop.ts
|
|
@@ -34033,16 +34232,16 @@ var init_desktop = () => {};
|
|
|
34033
34232
|
// src/editor-log.ts
|
|
34034
34233
|
import {
|
|
34035
34234
|
closeSync,
|
|
34036
|
-
existsSync as
|
|
34235
|
+
existsSync as existsSync14,
|
|
34037
34236
|
mkdirSync as mkdirSync9,
|
|
34038
34237
|
openSync,
|
|
34039
|
-
readFileSync as
|
|
34238
|
+
readFileSync as readFileSync16,
|
|
34040
34239
|
readSync,
|
|
34041
34240
|
rmSync as rmSync8,
|
|
34042
34241
|
statSync as statSync6,
|
|
34043
34242
|
writeFileSync as writeFileSync11
|
|
34044
34243
|
} from "node:fs";
|
|
34045
|
-
import { dirname as
|
|
34244
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
34046
34245
|
function editorLogPath(projectPath) {
|
|
34047
34246
|
return join18(projectPath, ".godot", "gdharness-editor.log");
|
|
34048
34247
|
}
|
|
@@ -34051,7 +34250,7 @@ function editorLogNotePath(projectPath) {
|
|
|
34051
34250
|
}
|
|
34052
34251
|
function readEditorLogNote(projectPath) {
|
|
34053
34252
|
try {
|
|
34054
|
-
const fields = asParams(JSON.parse(
|
|
34253
|
+
const fields = asParams(JSON.parse(readFileSync16(editorLogNotePath(projectPath), "utf8")));
|
|
34055
34254
|
const pid = readNumber(fields, "pid");
|
|
34056
34255
|
const startedAt = readString(fields, "startedAt");
|
|
34057
34256
|
return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
|
|
@@ -34062,7 +34261,7 @@ function readEditorLogNote(projectPath) {
|
|
|
34062
34261
|
function clearEditorLog(projectPath) {
|
|
34063
34262
|
try {
|
|
34064
34263
|
const path = editorLogPath(projectPath);
|
|
34065
|
-
mkdirSync9(
|
|
34264
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
34066
34265
|
rmSync8(path, { force: true });
|
|
34067
34266
|
rmSync8(editorLogNotePath(projectPath), { force: true });
|
|
34068
34267
|
} catch {}
|
|
@@ -34070,7 +34269,7 @@ function clearEditorLog(projectPath) {
|
|
|
34070
34269
|
function writeEditorLogNote(projectPath, pid) {
|
|
34071
34270
|
try {
|
|
34072
34271
|
const path = editorLogNotePath(projectPath);
|
|
34073
|
-
mkdirSync9(
|
|
34272
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
34074
34273
|
writeFileSync11(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
34075
34274
|
} catch {}
|
|
34076
34275
|
}
|
|
@@ -34092,7 +34291,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
34092
34291
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
34093
34292
|
}
|
|
34094
34293
|
const path = editorLogPath(projectPath);
|
|
34095
|
-
if (!
|
|
34294
|
+
if (!existsSync14(path)) {
|
|
34096
34295
|
return { kind: "not written" };
|
|
34097
34296
|
}
|
|
34098
34297
|
const log = new GameLog;
|
|
@@ -34104,7 +34303,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
34104
34303
|
function theFirstOf(path) {
|
|
34105
34304
|
const whole = statSync6(path).size;
|
|
34106
34305
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
34107
|
-
return { text:
|
|
34306
|
+
return { text: readFileSync16(path, "utf8") };
|
|
34108
34307
|
}
|
|
34109
34308
|
const handle = openSync(path, "r");
|
|
34110
34309
|
try {
|
|
@@ -37798,13 +37997,17 @@ function parseJUnit(xml) {
|
|
|
37798
37997
|
const suiteElements = top.name === "testsuite" ? [top] : top.children.filter((child) => child.name === "testsuite");
|
|
37799
37998
|
const suites = suiteElements.map((suite) => {
|
|
37800
37999
|
const cases = suite.children.filter((child) => child.name === "testcase").map((element) => caseOf(suite, element));
|
|
38000
|
+
const failures = cases.filter((entry) => entry.status === "failed").length;
|
|
38001
|
+
const errors = cases.filter((entry) => entry.status === "error").length;
|
|
37801
38002
|
return {
|
|
37802
38003
|
name: suite.attributes["name"] ?? "",
|
|
37803
38004
|
path: pathOf(suite),
|
|
37804
38005
|
tests: cases.length,
|
|
37805
38006
|
discovered: count(suite.attributes, "tests"),
|
|
37806
|
-
failures
|
|
37807
|
-
errors
|
|
38007
|
+
failures,
|
|
38008
|
+
errors,
|
|
38009
|
+
hookFailures: Math.max(0, count(suite.attributes, "failures") - failures),
|
|
38010
|
+
hookErrors: Math.max(0, count(suite.attributes, "errors") - errors),
|
|
37808
38011
|
skipped: cases.filter((entry) => entry.status === "skipped").length,
|
|
37809
38012
|
time: count(suite.attributes, "time"),
|
|
37810
38013
|
cases
|
|
@@ -37815,11 +38018,19 @@ function parseJUnit(xml) {
|
|
|
37815
38018
|
tests: all.length,
|
|
37816
38019
|
failures: all.filter((entry) => entry.status === "failed").length,
|
|
37817
38020
|
errors: all.filter((entry) => entry.status === "error").length,
|
|
38021
|
+
hookFailures: suites.reduce((sum, suite) => sum + suite.hookFailures, 0),
|
|
38022
|
+
hookErrors: suites.reduce((sum, suite) => sum + suite.hookErrors, 0),
|
|
37818
38023
|
skipped: all.filter((entry) => entry.status === "skipped").length,
|
|
37819
38024
|
time: suites.reduce((sum, suite) => sum + suite.time, 0),
|
|
37820
38025
|
suites
|
|
37821
38026
|
};
|
|
37822
38027
|
}
|
|
38028
|
+
function hookFailuresPrinted(printed) {
|
|
38029
|
+
return printed.flatMap((line) => {
|
|
38030
|
+
const found = HOOK_FAILURE.exec(line);
|
|
38031
|
+
return found === null ? [] : [{ hook: found[2] ?? "", path: found[3] ?? "", line: Number(found[4]), message: found[1] ?? "" }];
|
|
38032
|
+
});
|
|
38033
|
+
}
|
|
37823
38034
|
function whyNoReport(printed, asked) {
|
|
37824
38035
|
const missing = printed.map((line) => NO_SUCH_PATH.exec(line)).find((found) => found !== null);
|
|
37825
38036
|
if (missing !== undefined) {
|
|
@@ -37884,13 +38095,17 @@ function withActualsPrinted(failed, printed) {
|
|
|
37884
38095
|
at: match[5] ?? "",
|
|
37885
38096
|
...readDiff(match[1] ?? "")
|
|
37886
38097
|
}));
|
|
38098
|
+
const used = new Set;
|
|
37887
38099
|
return failed.map((entry) => {
|
|
37888
38100
|
if (entry.detail === null) {
|
|
37889
38101
|
return entry;
|
|
37890
38102
|
}
|
|
37891
38103
|
let detail = entry.detail;
|
|
37892
38104
|
let recovered = false;
|
|
37893
|
-
for (const diff of diffs) {
|
|
38105
|
+
for (const [index, diff] of diffs.entries()) {
|
|
38106
|
+
if (used.has(index)) {
|
|
38107
|
+
continue;
|
|
38108
|
+
}
|
|
37894
38109
|
const wrong = ` but was
|
|
37895
38110
|
'${diff.merged.replaceAll("\r", "")}'${diff.suffix}
|
|
37896
38111
|
at ${diff.at}`;
|
|
@@ -37899,6 +38114,7 @@ function withActualsPrinted(failed, printed) {
|
|
|
37899
38114
|
'${diff.actual}'${diff.suffix}
|
|
37900
38115
|
at ${diff.at}`);
|
|
37901
38116
|
recovered = true;
|
|
38117
|
+
used.add(index);
|
|
37902
38118
|
}
|
|
37903
38119
|
}
|
|
37904
38120
|
const quoted = QUOTED_EQUALITY.exec(detail);
|
|
@@ -37950,7 +38166,7 @@ function orphansPrinted(printed) {
|
|
|
37950
38166
|
suites
|
|
37951
38167
|
};
|
|
37952
38168
|
}
|
|
37953
|
-
var MalformedReportError, ENTITIES, ATTRIBUTE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary";
|
|
38169
|
+
var MalformedReportError, ENTITIES, ATTRIBUTE, HOOK_FAILURE, NOTHING_RAN, NO_SUCH_PATH, DISCOVERY_FAILED = "Script errors were detected during test discovery!", ESCAPE, LOCATION = `'[^'\\n]*' in \\S+:\\d+`, IGNORING_CASE = " \\(ignoring case\\)", PRINTED_ACTUAL, QUOTED_EQUALITY, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", VALUE_NOT_RECOVERED_NOTE = '[gdharness: the value after "but was" may not be the one the assertion found. gdUnit4 writes a failing string into its report as a character diff against the expected one with the marks dropped, which merges the two, and the runner printed no copy of this one to read the value back from.]', RENDERED_AWAY_NOTE = '[gdharness: the two sides print alike, so what tells them apart is something gdUnit4 does not print. The usual cause is BBCode in the expected string, which gdUnit4 renders as formatting instead of showing; the value after "but was" is the one the assertion found.]', MASKED_BRACKET = "[lb]", PRINTED_DICTIONARY, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary";
|
|
37954
38170
|
var init_junit = __esm(() => {
|
|
37955
38171
|
MalformedReportError = class MalformedReportError extends Error {
|
|
37956
38172
|
constructor(message) {
|
|
@@ -37960,8 +38176,9 @@ var init_junit = __esm(() => {
|
|
|
37960
38176
|
};
|
|
37961
38177
|
ENTITIES = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
|
|
37962
38178
|
ATTRIBUTE = /\s*([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/y;
|
|
38179
|
+
HOOK_FAILURE = /^\s*(.*?)\s*\tat '(before|after)' in (res:\/\/.+?):(\d+)\s*$/;
|
|
37963
38180
|
NOTHING_RAN = /no test cases found/i;
|
|
37964
|
-
NO_SUCH_PATH = /given directory or file does not exists:\s*(\
|
|
38181
|
+
NO_SUCH_PATH = /given directory or file does not exists:\s*(.+?)\s*$/i;
|
|
37965
38182
|
ESCAPE = String.fromCharCode(27);
|
|
37966
38183
|
PRINTED_ACTUAL = new RegExp(` but was\\n '((?:(?!\\tat ${LOCATION})[\\s\\S])*?)'((?:${ESCAPE}\\[[0-9;]*m)*)(${IGNORING_CASE})?((?:${ESCAPE}\\[[0-9;]*m)*)\\tat (${LOCATION})`, "g");
|
|
37967
38184
|
QUOTED_EQUALITY = new RegExp(`^Expecting(?: error message)?:\\n '([\\s\\S]*)'\\n but was\\n '([\\s\\S]*)'(?:${IGNORING_CASE})?\\n\\tat `);
|
|
@@ -37979,15 +38196,15 @@ var init_junit = __esm(() => {
|
|
|
37979
38196
|
"<DEL>": ""
|
|
37980
38197
|
};
|
|
37981
38198
|
PRINTED_DICTIONARY = /^\{\n\t[\s\S]*\n {2}\}$/;
|
|
37982
|
-
RUNNING_SUITE = /Run Test Suite:\s*(
|
|
38199
|
+
RUNNING_SUITE = /Run Test Suite:\s*(res:\/\/.+?\.(?:gd|cs))(?=\s|$)/;
|
|
37983
38200
|
ORPHANS = /(\d+)\s+orphans/;
|
|
37984
38201
|
});
|
|
37985
38202
|
|
|
37986
38203
|
// src/lsp_client.ts
|
|
37987
|
-
import { realpathSync as
|
|
38204
|
+
import { existsSync as existsSync15, realpathSync as realpathSync3 } from "node:fs";
|
|
37988
38205
|
import { readFile, realpath } from "node:fs/promises";
|
|
37989
38206
|
import { createConnection as createConnection3 } from "node:net";
|
|
37990
|
-
import { dirname as
|
|
38207
|
+
import { dirname as dirname13, join as join19, resolve as resolve4 } from "node:path";
|
|
37991
38208
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
37992
38209
|
function diagnosticsKey(uri) {
|
|
37993
38210
|
let decoded;
|
|
@@ -38000,7 +38217,7 @@ function diagnosticsKey(uri) {
|
|
|
38000
38217
|
return decoded;
|
|
38001
38218
|
}
|
|
38002
38219
|
try {
|
|
38003
|
-
return
|
|
38220
|
+
return realpathSync3.native(fileURLToPath3(decoded)).toLowerCase();
|
|
38004
38221
|
} catch {
|
|
38005
38222
|
return decoded.toLowerCase();
|
|
38006
38223
|
}
|
|
@@ -38017,8 +38234,10 @@ class GodotLSPClient {
|
|
|
38017
38234
|
connectPromise = null;
|
|
38018
38235
|
initialized = false;
|
|
38019
38236
|
rootPath = null;
|
|
38237
|
+
servedWorkspace = null;
|
|
38020
38238
|
diagnosticsWaiters = new Map;
|
|
38021
38239
|
documentVersions = new Map;
|
|
38240
|
+
documentTurns = new Map;
|
|
38022
38241
|
constructor(port = portFromEnv("GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT), host = "127.0.0.1") {
|
|
38023
38242
|
this.port = port;
|
|
38024
38243
|
this.host = host;
|
|
@@ -38115,7 +38334,7 @@ class GodotLSPClient {
|
|
|
38115
38334
|
if (this.initialized) {
|
|
38116
38335
|
return;
|
|
38117
38336
|
}
|
|
38118
|
-
const rootPath = this.rootPath ??
|
|
38337
|
+
const rootPath = this.rootPath ?? dirname13(resolve4(filePath));
|
|
38119
38338
|
await this.initialize(rootPath);
|
|
38120
38339
|
}
|
|
38121
38340
|
async sendRequest(method, params) {
|
|
@@ -38190,6 +38409,10 @@ class GodotLSPClient {
|
|
|
38190
38409
|
}
|
|
38191
38410
|
}
|
|
38192
38411
|
}
|
|
38412
|
+
if (message["method"] === "gdscript_client/changeWorkspace") {
|
|
38413
|
+
const path = message["params"]?.["path"];
|
|
38414
|
+
this.servedWorkspace = typeof path === "string" ? path : null;
|
|
38415
|
+
}
|
|
38193
38416
|
if (message["method"] === "textDocument/publishDiagnostics") {
|
|
38194
38417
|
const params = message["params"];
|
|
38195
38418
|
const paramsObject = params && typeof params === "object" ? params : null;
|
|
@@ -38292,10 +38515,14 @@ class GodotLSPClient {
|
|
|
38292
38515
|
this.sendNotification("textDocument/didClose", { textDocument: { uri } });
|
|
38293
38516
|
} catch {}
|
|
38294
38517
|
}
|
|
38518
|
+
forgetServedWorkspace() {
|
|
38519
|
+
this.servedWorkspace = null;
|
|
38520
|
+
}
|
|
38295
38521
|
async initialize(rootPath) {
|
|
38296
38522
|
await this.ensureConnected();
|
|
38297
38523
|
const resolvedRootPath = resolve4(rootPath);
|
|
38298
38524
|
const rootUri = pathToFileURL(resolvedRootPath).href;
|
|
38525
|
+
this.forgetServedWorkspace();
|
|
38299
38526
|
const result = await this.sendRequest("initialize", {
|
|
38300
38527
|
processId: process.pid,
|
|
38301
38528
|
rootPath: resolvedRootPath,
|
|
@@ -38321,60 +38548,82 @@ class GodotLSPClient {
|
|
|
38321
38548
|
}
|
|
38322
38549
|
]
|
|
38323
38550
|
});
|
|
38551
|
+
const served = this.servedWorkspace;
|
|
38552
|
+
if (served !== null && !isSameDirectory(served, resolvedRootPath)) {
|
|
38553
|
+
throw new AnotherProjectsServer(this.port, served, resolvedRootPath);
|
|
38554
|
+
}
|
|
38324
38555
|
this.sendNotification("initialized", {});
|
|
38325
38556
|
this.initialized = true;
|
|
38326
38557
|
this.rootPath = resolvedRootPath;
|
|
38327
38558
|
return result;
|
|
38328
38559
|
}
|
|
38329
|
-
async
|
|
38330
|
-
await this.ensureConnected();
|
|
38331
|
-
await this.ensureInitializedForFile(filePath);
|
|
38560
|
+
async withDocument(filePath, work) {
|
|
38332
38561
|
const key = diagnosticsKey(this.toFileUri(filePath));
|
|
38333
|
-
const
|
|
38334
|
-
|
|
38335
|
-
|
|
38336
|
-
|
|
38337
|
-
}
|
|
38338
|
-
const timer = setTimeout(() => {
|
|
38339
|
-
this.diagnosticsWaiters.delete(key);
|
|
38340
|
-
rejectDiagnostics(new Error(`Godot published no diagnostics for ${key} within ${DIAGNOSTICS_TIMEOUT_MS}ms. ` + "The language server may not be running, or may not have this file in its workspace."));
|
|
38341
|
-
}, DIAGNOSTICS_TIMEOUT_MS);
|
|
38342
|
-
this.diagnosticsWaiters.set(key, {
|
|
38343
|
-
resolve: resolveDiagnostics,
|
|
38344
|
-
reject: rejectDiagnostics,
|
|
38345
|
-
timer
|
|
38346
|
-
});
|
|
38562
|
+
const before = this.documentTurns.get(key) ?? Promise.resolve();
|
|
38563
|
+
let done = () => {};
|
|
38564
|
+
const mine = new Promise((finished) => {
|
|
38565
|
+
done = finished;
|
|
38347
38566
|
});
|
|
38348
|
-
|
|
38567
|
+
const turn = before.then(() => mine);
|
|
38568
|
+
this.documentTurns.set(key, turn);
|
|
38569
|
+
await before;
|
|
38349
38570
|
try {
|
|
38350
|
-
|
|
38351
|
-
return await diagnosticsPromise;
|
|
38352
|
-
} catch (error) {
|
|
38353
|
-
const waiter = this.diagnosticsWaiters.get(key);
|
|
38354
|
-
if (waiter) {
|
|
38355
|
-
clearTimeout(waiter.timer);
|
|
38356
|
-
this.diagnosticsWaiters.delete(key);
|
|
38357
|
-
}
|
|
38358
|
-
throw error;
|
|
38571
|
+
return await work();
|
|
38359
38572
|
} finally {
|
|
38360
|
-
|
|
38361
|
-
|
|
38573
|
+
done();
|
|
38574
|
+
if (this.documentTurns.get(key) === turn) {
|
|
38575
|
+
this.documentTurns.delete(key);
|
|
38362
38576
|
}
|
|
38363
38577
|
}
|
|
38364
38578
|
}
|
|
38365
|
-
async
|
|
38579
|
+
async getDiagnostics(filePath, content) {
|
|
38366
38580
|
await this.ensureConnected();
|
|
38367
38581
|
await this.ensureInitializedForFile(filePath);
|
|
38368
|
-
|
|
38369
|
-
|
|
38370
|
-
|
|
38371
|
-
|
|
38372
|
-
|
|
38373
|
-
|
|
38582
|
+
return this.withDocument(filePath, async () => {
|
|
38583
|
+
const key = diagnosticsKey(this.toFileUri(filePath));
|
|
38584
|
+
const diagnosticsPromise = new Promise((resolveDiagnostics, rejectDiagnostics) => {
|
|
38585
|
+
const timer = setTimeout(() => {
|
|
38586
|
+
this.diagnosticsWaiters.delete(key);
|
|
38587
|
+
rejectDiagnostics(new Error(`Godot published no diagnostics for ${key} within ${DIAGNOSTICS_TIMEOUT_MS}ms. ` + "The language server may not be running, or may not have this file in its workspace."));
|
|
38588
|
+
}, DIAGNOSTICS_TIMEOUT_MS);
|
|
38589
|
+
this.diagnosticsWaiters.set(key, {
|
|
38590
|
+
resolve: resolveDiagnostics,
|
|
38591
|
+
reject: rejectDiagnostics,
|
|
38592
|
+
timer
|
|
38593
|
+
});
|
|
38374
38594
|
});
|
|
38375
|
-
|
|
38376
|
-
|
|
38377
|
-
|
|
38595
|
+
let opened = null;
|
|
38596
|
+
try {
|
|
38597
|
+
opened = this.syncDocument(filePath, content);
|
|
38598
|
+
return await diagnosticsPromise;
|
|
38599
|
+
} catch (error) {
|
|
38600
|
+
const waiter = this.diagnosticsWaiters.get(key);
|
|
38601
|
+
if (waiter) {
|
|
38602
|
+
clearTimeout(waiter.timer);
|
|
38603
|
+
this.diagnosticsWaiters.delete(key);
|
|
38604
|
+
}
|
|
38605
|
+
throw error;
|
|
38606
|
+
} finally {
|
|
38607
|
+
if (opened !== null) {
|
|
38608
|
+
this.closeDocument(opened);
|
|
38609
|
+
}
|
|
38610
|
+
}
|
|
38611
|
+
});
|
|
38612
|
+
}
|
|
38613
|
+
async getCompletions(filePath, content, line, character) {
|
|
38614
|
+
await this.ensureConnected();
|
|
38615
|
+
await this.ensureInitializedForFile(filePath);
|
|
38616
|
+
const result = await this.withDocument(filePath, async () => {
|
|
38617
|
+
const uri = this.syncDocument(filePath, content);
|
|
38618
|
+
try {
|
|
38619
|
+
return await this.sendRequest("textDocument/completion", {
|
|
38620
|
+
textDocument: { uri },
|
|
38621
|
+
position: { line, character }
|
|
38622
|
+
});
|
|
38623
|
+
} finally {
|
|
38624
|
+
this.closeDocument(uri);
|
|
38625
|
+
}
|
|
38626
|
+
});
|
|
38378
38627
|
if (Array.isArray(result)) {
|
|
38379
38628
|
return result;
|
|
38380
38629
|
}
|
|
@@ -38389,28 +38638,31 @@ class GodotLSPClient {
|
|
|
38389
38638
|
async getHover(filePath, content, line, character) {
|
|
38390
38639
|
await this.ensureConnected();
|
|
38391
38640
|
await this.ensureInitializedForFile(filePath);
|
|
38392
|
-
|
|
38393
|
-
|
|
38394
|
-
|
|
38395
|
-
textDocument
|
|
38396
|
-
|
|
38397
|
-
|
|
38398
|
-
|
|
38399
|
-
|
|
38400
|
-
|
|
38641
|
+
return this.withDocument(filePath, async () => {
|
|
38642
|
+
const uri = this.syncDocument(filePath, content);
|
|
38643
|
+
try {
|
|
38644
|
+
return await this.sendRequest("textDocument/hover", {
|
|
38645
|
+
textDocument: { uri },
|
|
38646
|
+
position: { line, character }
|
|
38647
|
+
});
|
|
38648
|
+
} finally {
|
|
38649
|
+
this.closeDocument(uri);
|
|
38650
|
+
}
|
|
38651
|
+
});
|
|
38401
38652
|
}
|
|
38402
38653
|
async getDocumentSymbols(filePath, content) {
|
|
38403
38654
|
await this.ensureConnected();
|
|
38404
38655
|
await this.ensureInitializedForFile(filePath);
|
|
38405
|
-
const
|
|
38406
|
-
|
|
38407
|
-
|
|
38408
|
-
|
|
38409
|
-
|
|
38410
|
-
|
|
38411
|
-
|
|
38412
|
-
|
|
38413
|
-
|
|
38656
|
+
const result = await this.withDocument(filePath, async () => {
|
|
38657
|
+
const uri = this.syncDocument(filePath, content);
|
|
38658
|
+
try {
|
|
38659
|
+
return await this.sendRequest("textDocument/documentSymbol", {
|
|
38660
|
+
textDocument: { uri }
|
|
38661
|
+
});
|
|
38662
|
+
} finally {
|
|
38663
|
+
this.closeDocument(uri);
|
|
38664
|
+
}
|
|
38665
|
+
});
|
|
38414
38666
|
return Array.isArray(result) ? result : [];
|
|
38415
38667
|
}
|
|
38416
38668
|
isConnected() {
|
|
@@ -38442,36 +38694,39 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
38442
38694
|
try {
|
|
38443
38695
|
projectPath = await realpath(requestedProjectPath);
|
|
38444
38696
|
} catch {
|
|
38445
|
-
throw new
|
|
38697
|
+
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
38698
|
+
}
|
|
38699
|
+
if (!existsSync15(join19(projectPath, "project.godot"))) {
|
|
38700
|
+
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
38446
38701
|
}
|
|
38447
38702
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
38448
38703
|
if (!contained.ok) {
|
|
38449
|
-
throw new
|
|
38704
|
+
throw new ArgumentRefusal(contained.reason);
|
|
38450
38705
|
}
|
|
38451
38706
|
let scriptPath;
|
|
38452
38707
|
try {
|
|
38453
38708
|
scriptPath = await realpath(contained.absolutePath);
|
|
38454
38709
|
} catch {
|
|
38455
|
-
throw new
|
|
38710
|
+
throw new ArgumentRefusal(`Script file does not exist: ${contained.absolutePath}`);
|
|
38456
38711
|
}
|
|
38457
38712
|
if (!isWithinRoot(projectPath, scriptPath)) {
|
|
38458
|
-
throw new
|
|
38713
|
+
throw new ArgumentRefusal("scriptPath resolves outside the project root boundary.");
|
|
38459
38714
|
}
|
|
38460
38715
|
return { projectPath, scriptPath };
|
|
38461
38716
|
}
|
|
38462
38717
|
async function handleLSPTool(client, toolName, args) {
|
|
38463
38718
|
try {
|
|
38464
38719
|
if (!args || typeof args !== "object") {
|
|
38465
|
-
throw new
|
|
38720
|
+
throw new ArgumentRefusal("Tool arguments must be an object.");
|
|
38466
38721
|
}
|
|
38467
38722
|
const parsedArgs = args;
|
|
38468
38723
|
const projectPathValue = parsedArgs["projectPath"];
|
|
38469
38724
|
const scriptPathValue = parsedArgs["scriptPath"];
|
|
38470
38725
|
if (typeof projectPathValue !== "string" || projectPathValue.length === 0) {
|
|
38471
|
-
throw new
|
|
38726
|
+
throw new ArgumentRefusal("Missing required argument: projectPath");
|
|
38472
38727
|
}
|
|
38473
38728
|
if (typeof scriptPathValue !== "string" || scriptPathValue.length === 0) {
|
|
38474
|
-
throw new
|
|
38729
|
+
throw new ArgumentRefusal("Missing required argument: scriptPath");
|
|
38475
38730
|
}
|
|
38476
38731
|
const { projectPath, scriptPath } = await resolveLSPPaths(projectPathValue, scriptPathValue);
|
|
38477
38732
|
const content = await readFile(scriptPath, "utf8");
|
|
@@ -38485,7 +38740,7 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
38485
38740
|
const line = Number(parsedArgs["line"]);
|
|
38486
38741
|
const character = Number(parsedArgs["character"]);
|
|
38487
38742
|
if (!Number.isFinite(line) || !Number.isFinite(character)) {
|
|
38488
|
-
throw new
|
|
38743
|
+
throw new ArgumentRefusal("Arguments line and character must be numbers.");
|
|
38489
38744
|
}
|
|
38490
38745
|
const completions = await client.getCompletions(scriptPath, content, line, character);
|
|
38491
38746
|
return asToolResponse({ completions });
|
|
@@ -38494,7 +38749,7 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
38494
38749
|
const line = Number(parsedArgs["line"]);
|
|
38495
38750
|
const character = Number(parsedArgs["character"]);
|
|
38496
38751
|
if (!Number.isFinite(line) || !Number.isFinite(character)) {
|
|
38497
|
-
throw new
|
|
38752
|
+
throw new ArgumentRefusal("Arguments line and character must be numbers.");
|
|
38498
38753
|
}
|
|
38499
38754
|
const hover = await client.getHover(scriptPath, content, line, character);
|
|
38500
38755
|
return asToolResponse({ hover });
|
|
@@ -38508,20 +38763,31 @@ async function handleLSPTool(client, toolName, args) {
|
|
|
38508
38763
|
}
|
|
38509
38764
|
} catch (error) {
|
|
38510
38765
|
return asToolResponse({
|
|
38511
|
-
error: normalizeLSPError(error, client.port)
|
|
38766
|
+
error: normalizeLSPError(error, client.port),
|
|
38767
|
+
...error instanceof AnotherProjectsServer ? { serves: error.serves } : {},
|
|
38768
|
+
...error instanceof ArgumentRefusal ? { refusedArguments: true } : {}
|
|
38512
38769
|
});
|
|
38513
38770
|
}
|
|
38514
38771
|
}
|
|
38515
|
-
var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000;
|
|
38772
|
+
var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, ArgumentRefusal, AnotherProjectsServer;
|
|
38516
38773
|
var init_lsp_client = __esm(() => {
|
|
38517
38774
|
init_errors();
|
|
38518
38775
|
init_framing();
|
|
38519
38776
|
init_paths();
|
|
38520
38777
|
init_ports();
|
|
38778
|
+
ArgumentRefusal = class ArgumentRefusal extends Refusal {
|
|
38779
|
+
};
|
|
38780
|
+
AnotherProjectsServer = class AnotherProjectsServer extends Refusal {
|
|
38781
|
+
serves;
|
|
38782
|
+
constructor(port, serves, asked) {
|
|
38783
|
+
super(`The language server on port ${port} belongs to the editor of ${serves}, not ${asked}.`);
|
|
38784
|
+
this.serves = serves;
|
|
38785
|
+
}
|
|
38786
|
+
};
|
|
38521
38787
|
});
|
|
38522
38788
|
|
|
38523
38789
|
// src/outside.ts
|
|
38524
|
-
import { spawn as
|
|
38790
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
38525
38791
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
38526
38792
|
function changesFrom(env, base) {
|
|
38527
38793
|
const changes = {};
|
|
@@ -38554,7 +38820,7 @@ function readLaunched(printed) {
|
|
|
38554
38820
|
}
|
|
38555
38821
|
async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
|
|
38556
38822
|
return await new Promise((resolve) => {
|
|
38557
|
-
const launcher =
|
|
38823
|
+
const launcher = spawn3(runtime, [keeper, "launch"], {
|
|
38558
38824
|
stdio: ["pipe", "pipe", "pipe"],
|
|
38559
38825
|
windowsHide: true
|
|
38560
38826
|
});
|
|
@@ -38589,8 +38855,8 @@ var init_outside = __esm(() => {
|
|
|
38589
38855
|
});
|
|
38590
38856
|
|
|
38591
38857
|
// src/process-time.ts
|
|
38592
|
-
import { execFile as
|
|
38593
|
-
import { promisify as
|
|
38858
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
38859
|
+
import { promisify as promisify3 } from "node:util";
|
|
38594
38860
|
async function cpuSecondsOf(pid) {
|
|
38595
38861
|
try {
|
|
38596
38862
|
return process.platform === "win32" ? await secondsOnWindows(pid) : await askPosix2(pid);
|
|
@@ -38604,7 +38870,7 @@ async function secondsOnWindows(pid) {
|
|
|
38604
38870
|
return Number.isFinite(seconds) ? seconds : undefined;
|
|
38605
38871
|
}
|
|
38606
38872
|
async function askPosix2(pid) {
|
|
38607
|
-
const { stdout } = await
|
|
38873
|
+
const { stdout } = await run3("ps", ["-o", "time=", "-p", String(pid)], { timeout: ASK_TIMEOUT_MS2 });
|
|
38608
38874
|
return secondsFromClock(stdout.trim());
|
|
38609
38875
|
}
|
|
38610
38876
|
function secondsFromClock(printed) {
|
|
@@ -38622,20 +38888,20 @@ function secondsFromClock(printed) {
|
|
|
38622
38888
|
const extra = days === null ? 0 : Number(days) * 86400;
|
|
38623
38889
|
return Number.isFinite(extra) ? seconds + extra : undefined;
|
|
38624
38890
|
}
|
|
38625
|
-
var
|
|
38891
|
+
var run3, ASK_TIMEOUT_MS2 = 6000;
|
|
38626
38892
|
var init_process_time = __esm(() => {
|
|
38627
38893
|
init_windows_ask();
|
|
38628
|
-
|
|
38894
|
+
run3 = promisify3(execFile3);
|
|
38629
38895
|
});
|
|
38630
38896
|
|
|
38631
38897
|
// src/project-scan.ts
|
|
38632
|
-
import { existsSync as
|
|
38633
|
-
import { join as
|
|
38898
|
+
import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync17 } from "node:fs";
|
|
38899
|
+
import { join as join20 } from "node:path";
|
|
38634
38900
|
function skipped(name) {
|
|
38635
38901
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
38636
38902
|
}
|
|
38637
38903
|
function steppedOver(directory) {
|
|
38638
|
-
return
|
|
38904
|
+
return existsSync16(join20(directory, ".gdignore"));
|
|
38639
38905
|
}
|
|
38640
38906
|
function projectStructure(projectPath) {
|
|
38641
38907
|
const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
|
|
@@ -38648,7 +38914,7 @@ function projectStructure(projectPath) {
|
|
|
38648
38914
|
continue;
|
|
38649
38915
|
}
|
|
38650
38916
|
if (entry.isDirectory()) {
|
|
38651
|
-
visit(
|
|
38917
|
+
visit(join20(directory, entry.name));
|
|
38652
38918
|
} else if (entry.isFile()) {
|
|
38653
38919
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
38654
38920
|
if (extension === "tscn") {
|
|
@@ -38678,14 +38944,14 @@ function scriptsWithoutUid(projectPath) {
|
|
|
38678
38944
|
}
|
|
38679
38945
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
38680
38946
|
if (entry.isDirectory()) {
|
|
38681
|
-
visit(
|
|
38947
|
+
visit(join20(directory, entry.name), spelled);
|
|
38682
38948
|
continue;
|
|
38683
38949
|
}
|
|
38684
38950
|
if (!entry.isFile()) {
|
|
38685
38951
|
continue;
|
|
38686
38952
|
}
|
|
38687
38953
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
38688
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
38954
|
+
if (CARRIES_A_UID.has(extension) && !existsSync16(join20(directory, `${entry.name}.uid`))) {
|
|
38689
38955
|
found.push(spelled);
|
|
38690
38956
|
}
|
|
38691
38957
|
}
|
|
@@ -38720,7 +38986,7 @@ function searchProject(projectPath, options) {
|
|
|
38720
38986
|
if (skipped(entry.name)) {
|
|
38721
38987
|
continue;
|
|
38722
38988
|
}
|
|
38723
|
-
const entryPath =
|
|
38989
|
+
const entryPath = join20(directory, entry.name);
|
|
38724
38990
|
if (entry.isDirectory()) {
|
|
38725
38991
|
visit(entryPath);
|
|
38726
38992
|
continue;
|
|
@@ -38731,7 +38997,7 @@ function searchProject(projectPath, options) {
|
|
|
38731
38997
|
}
|
|
38732
38998
|
result.summary.files_searched += 1;
|
|
38733
38999
|
const matches = [];
|
|
38734
|
-
for (const [index, line] of
|
|
39000
|
+
for (const [index, line] of readFileSync17(entryPath, "utf8").split(`
|
|
38735
39001
|
`).entries()) {
|
|
38736
39002
|
if (full()) {
|
|
38737
39003
|
break;
|
|
@@ -38760,15 +39026,15 @@ var init_project_scan = __esm(() => {
|
|
|
38760
39026
|
});
|
|
38761
39027
|
|
|
38762
39028
|
// src/restart-note.ts
|
|
38763
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
38764
|
-
import { dirname as
|
|
39029
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
|
39030
|
+
import { dirname as dirname14, join as join21 } from "node:path";
|
|
38765
39031
|
function restartNotePath(projectPath) {
|
|
38766
|
-
return
|
|
39032
|
+
return join21(projectPath, ".godot", "gdharness-restart.json");
|
|
38767
39033
|
}
|
|
38768
39034
|
function noteRestartBegun(note) {
|
|
38769
39035
|
const path = restartNotePath(note.projectPath);
|
|
38770
39036
|
try {
|
|
38771
|
-
mkdirSync10(
|
|
39037
|
+
mkdirSync10(dirname14(path), { recursive: true });
|
|
38772
39038
|
writeFileSync12(path, `${JSON.stringify(note, null, 2)}
|
|
38773
39039
|
`, "utf8");
|
|
38774
39040
|
} catch (error) {
|
|
@@ -38777,7 +39043,7 @@ function noteRestartBegun(note) {
|
|
|
38777
39043
|
}
|
|
38778
39044
|
function restartOwed(projectPath) {
|
|
38779
39045
|
try {
|
|
38780
|
-
const parsed = JSON.parse(
|
|
39046
|
+
const parsed = JSON.parse(readFileSync18(restartNotePath(projectPath), "utf8"));
|
|
38781
39047
|
if (typeof parsed !== "object" || parsed === null) {
|
|
38782
39048
|
return null;
|
|
38783
39049
|
}
|
|
@@ -38802,31 +39068,31 @@ var init_restart_note = __esm(() => {
|
|
|
38802
39068
|
});
|
|
38803
39069
|
|
|
38804
39070
|
// src/run-record.ts
|
|
38805
|
-
import { execFile as
|
|
39071
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
38806
39072
|
import { createHash as createHash4 } from "node:crypto";
|
|
38807
39073
|
import {
|
|
38808
39074
|
mkdirSync as mkdirSync11,
|
|
38809
39075
|
openSync as openSync2,
|
|
38810
39076
|
readdirSync as readdirSync8,
|
|
38811
|
-
readFileSync as
|
|
38812
|
-
realpathSync as
|
|
39077
|
+
readFileSync as readFileSync19,
|
|
39078
|
+
realpathSync as realpathSync4,
|
|
38813
39079
|
rmSync as rmSync10,
|
|
38814
39080
|
statSync as statSync7,
|
|
38815
39081
|
writeFileSync as writeFileSync13
|
|
38816
39082
|
} from "node:fs";
|
|
38817
|
-
import { basename as basename4, dirname as
|
|
38818
|
-
import { promisify as
|
|
39083
|
+
import { basename as basename4, dirname as dirname15, join as join22, resolve as resolve5 } from "node:path";
|
|
39084
|
+
import { promisify as promisify4 } from "node:util";
|
|
38819
39085
|
function runsDirectory() {
|
|
38820
|
-
return
|
|
39086
|
+
return join22(runtimeDirectory(), "runs");
|
|
38821
39087
|
}
|
|
38822
39088
|
function realAsFarAsItExists(path) {
|
|
38823
39089
|
const missing = [];
|
|
38824
39090
|
let at = path;
|
|
38825
39091
|
for (;; ) {
|
|
38826
39092
|
try {
|
|
38827
|
-
return
|
|
39093
|
+
return join22(realpathSync4.native(at), ...missing);
|
|
38828
39094
|
} catch {
|
|
38829
|
-
const parent =
|
|
39095
|
+
const parent = dirname15(at);
|
|
38830
39096
|
if (parent === at) {
|
|
38831
39097
|
return path;
|
|
38832
39098
|
}
|
|
@@ -38841,15 +39107,18 @@ function projectKey(projectPath) {
|
|
|
38841
39107
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
38842
39108
|
}
|
|
38843
39109
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
38844
|
-
return
|
|
39110
|
+
return join22(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
38845
39111
|
}
|
|
38846
39112
|
function openTranscript(startedAt) {
|
|
38847
39113
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
38848
|
-
const path =
|
|
39114
|
+
const path = join22(runsDirectory(), `run-${startedAt}.log`);
|
|
38849
39115
|
return { path, fd: openSync2(path, "a") };
|
|
38850
39116
|
}
|
|
39117
|
+
function noteIsAbout(note, playing) {
|
|
39118
|
+
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
39119
|
+
}
|
|
38851
39120
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
38852
|
-
return
|
|
39121
|
+
return join22(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
38853
39122
|
}
|
|
38854
39123
|
function writeEditorRunNote(note) {
|
|
38855
39124
|
mkdirSync11(runsDirectory(), { recursive: true });
|
|
@@ -38857,7 +39126,7 @@ function writeEditorRunNote(note) {
|
|
|
38857
39126
|
}
|
|
38858
39127
|
function readEditorRunNote(projectPath) {
|
|
38859
39128
|
for (const directory of runtimeDirectories()) {
|
|
38860
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
39129
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join22(directory, "runs", "editor-run.json"));
|
|
38861
39130
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
38862
39131
|
return note;
|
|
38863
39132
|
}
|
|
@@ -38867,7 +39136,7 @@ function readEditorRunNote(projectPath) {
|
|
|
38867
39136
|
function editorNoteAt(path) {
|
|
38868
39137
|
let parsed;
|
|
38869
39138
|
try {
|
|
38870
|
-
parsed = JSON.parse(
|
|
39139
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
38871
39140
|
} catch {
|
|
38872
39141
|
return null;
|
|
38873
39142
|
}
|
|
@@ -38881,7 +39150,9 @@ function editorNoteAt(path) {
|
|
|
38881
39150
|
if (typeof projectPath !== "string" || typeof transcript !== "string" || typeof startedAt !== "number") {
|
|
38882
39151
|
return null;
|
|
38883
39152
|
}
|
|
38884
|
-
|
|
39153
|
+
const play = fields["play"];
|
|
39154
|
+
const which = typeof play === "object" && play !== null && typeof play["play"] === "number" && typeof play["editorPid"] === "number" ? play : undefined;
|
|
39155
|
+
return { projectPath, transcript, startedAt, ...which === undefined ? {} : { play: which } };
|
|
38885
39156
|
}
|
|
38886
39157
|
function readRunRecord(projectPath) {
|
|
38887
39158
|
for (const directory of runtimeDirectories()) {
|
|
@@ -38889,7 +39160,7 @@ function readRunRecord(projectPath) {
|
|
|
38889
39160
|
if (own !== null) {
|
|
38890
39161
|
return own;
|
|
38891
39162
|
}
|
|
38892
|
-
const shared = recordAt(
|
|
39163
|
+
const shared = recordAt(join22(directory, "runs", SHARED_NOTE));
|
|
38893
39164
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
38894
39165
|
return shared;
|
|
38895
39166
|
}
|
|
@@ -38901,13 +39172,13 @@ function everyRunRecord() {
|
|
|
38901
39172
|
for (const directory of runtimeDirectories()) {
|
|
38902
39173
|
let entries;
|
|
38903
39174
|
try {
|
|
38904
|
-
entries = readdirSync8(
|
|
39175
|
+
entries = readdirSync8(join22(directory, "runs"));
|
|
38905
39176
|
} catch {
|
|
38906
39177
|
continue;
|
|
38907
39178
|
}
|
|
38908
39179
|
for (const entry of entries) {
|
|
38909
39180
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
38910
|
-
const record = recordAt(
|
|
39181
|
+
const record = recordAt(join22(directory, "runs", entry));
|
|
38911
39182
|
if (record !== null) {
|
|
38912
39183
|
found.push(record);
|
|
38913
39184
|
}
|
|
@@ -38919,7 +39190,7 @@ function everyRunRecord() {
|
|
|
38919
39190
|
function recordAt(path) {
|
|
38920
39191
|
let parsed;
|
|
38921
39192
|
try {
|
|
38922
|
-
parsed = JSON.parse(
|
|
39193
|
+
parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
38923
39194
|
} catch {
|
|
38924
39195
|
return null;
|
|
38925
39196
|
}
|
|
@@ -38965,7 +39236,7 @@ async function runningAs(pid) {
|
|
|
38965
39236
|
const began = await startedAt(pid, null);
|
|
38966
39237
|
const when = began === null ? {} : { startedAt: began };
|
|
38967
39238
|
if (process.platform === "linux") {
|
|
38968
|
-
const raw =
|
|
39239
|
+
const raw = readFileSync19(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
38969
39240
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
38970
39241
|
}
|
|
38971
39242
|
const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
@@ -39093,7 +39364,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
39093
39364
|
}
|
|
39094
39365
|
function clearRunRecord(projectPath) {
|
|
39095
39366
|
for (const directory of runtimeDirectories()) {
|
|
39096
|
-
const shared =
|
|
39367
|
+
const shared = join22(directory, "runs", SHARED_NOTE);
|
|
39097
39368
|
const sharedRecord = recordAt(shared);
|
|
39098
39369
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
39099
39370
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -39123,7 +39394,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
39123
39394
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
39124
39395
|
continue;
|
|
39125
39396
|
}
|
|
39126
|
-
const path =
|
|
39397
|
+
const path = join22(directory, entry);
|
|
39127
39398
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
39128
39399
|
continue;
|
|
39129
39400
|
}
|
|
@@ -39138,31 +39409,31 @@ var init_run_record = __esm(() => {
|
|
|
39138
39409
|
init_process_children();
|
|
39139
39410
|
init_runtime_client();
|
|
39140
39411
|
init_windows_ask();
|
|
39141
|
-
execFileAsync =
|
|
39412
|
+
execFileAsync = promisify4(execFile4);
|
|
39142
39413
|
});
|
|
39143
39414
|
|
|
39144
39415
|
// src/server.ts
|
|
39145
|
-
import { execFile as
|
|
39416
|
+
import { execFile as execFile5, spawn as spawn4 } from "node:child_process";
|
|
39146
39417
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
39147
39418
|
import {
|
|
39148
39419
|
appendFileSync,
|
|
39149
39420
|
closeSync as closeSync2,
|
|
39150
39421
|
constants,
|
|
39151
39422
|
copyFileSync,
|
|
39152
|
-
existsSync as
|
|
39423
|
+
existsSync as existsSync17,
|
|
39153
39424
|
fstatSync,
|
|
39154
39425
|
mkdirSync as mkdirSync12,
|
|
39155
39426
|
openSync as openSync3,
|
|
39156
39427
|
readdirSync as readdirSync9,
|
|
39157
|
-
readFileSync as
|
|
39428
|
+
readFileSync as readFileSync20,
|
|
39158
39429
|
readSync as readSync2,
|
|
39159
39430
|
rmdirSync as rmdirSync2,
|
|
39160
39431
|
statSync as statSync8
|
|
39161
39432
|
} from "node:fs";
|
|
39162
|
-
import { basename as basename5, dirname as
|
|
39433
|
+
import { basename as basename5, dirname as dirname16, extname as extname2, isAbsolute as isAbsolute3, join as join23, normalize as normalize2, relative as relative3 } from "node:path";
|
|
39163
39434
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
39164
39435
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
39165
|
-
import { promisify as
|
|
39436
|
+
import { promisify as promisify5 } from "node:util";
|
|
39166
39437
|
function captureDestinationRefusal(outputPath, projects) {
|
|
39167
39438
|
if (/^(res|user):\/\//.test(outputPath)) {
|
|
39168
39439
|
return `outputPath ${outputPath} is a Godot path; a capture is saved outside the project, at an absolute path on this machine.`;
|
|
@@ -39173,16 +39444,16 @@ function captureDestinationRefusal(outputPath, projects) {
|
|
|
39173
39444
|
if (extname2(outputPath).toLowerCase() !== ".png") {
|
|
39174
39445
|
return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
|
|
39175
39446
|
}
|
|
39176
|
-
const directory = realPathOr(
|
|
39177
|
-
if (!
|
|
39178
|
-
return `outputPath's directory ${
|
|
39447
|
+
const directory = realPathOr(dirname16(outputPath));
|
|
39448
|
+
if (!existsSync17(directory)) {
|
|
39449
|
+
return `outputPath's directory ${dirname16(outputPath)} does not exist; a capture creates no directories.`;
|
|
39179
39450
|
}
|
|
39180
|
-
const where =
|
|
39451
|
+
const where = join23(directory, basename5(outputPath));
|
|
39181
39452
|
const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
39182
39453
|
if (inside !== undefined) {
|
|
39183
39454
|
return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
|
|
39184
39455
|
}
|
|
39185
|
-
if (
|
|
39456
|
+
if (existsSync17(where)) {
|
|
39186
39457
|
return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
|
|
39187
39458
|
}
|
|
39188
39459
|
return null;
|
|
@@ -39213,16 +39484,18 @@ function describeHalt(halt) {
|
|
|
39213
39484
|
function exited(run) {
|
|
39214
39485
|
return run.exitCode !== null || run.exitSignal !== null;
|
|
39215
39486
|
}
|
|
39216
|
-
function endingOf(failed) {
|
|
39217
|
-
return {
|
|
39218
|
-
exitCode: typeof failed.code === "number" ? failed.code : null,
|
|
39219
|
-
exitSignal: typeof failed.signal === "string" ? failed.signal : null,
|
|
39220
|
-
failure: typeof failed.code === "string" ? failed.code : null
|
|
39221
|
-
};
|
|
39222
|
-
}
|
|
39223
39487
|
function howItExited(run) {
|
|
39224
39488
|
return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}, with no exit code`;
|
|
39225
39489
|
}
|
|
39490
|
+
function errorsUnread(run) {
|
|
39491
|
+
return run.throughEditor && typeof run.errorReport !== "string";
|
|
39492
|
+
}
|
|
39493
|
+
function cleanVerdict(run) {
|
|
39494
|
+
if (run.consoleLost === true || errorsUnread(run)) {
|
|
39495
|
+
return;
|
|
39496
|
+
}
|
|
39497
|
+
return run.log.count("error") === 0;
|
|
39498
|
+
}
|
|
39226
39499
|
function stillRunning(run) {
|
|
39227
39500
|
if (run === null || exited(run) || run.endedUnwatched === true) {
|
|
39228
39501
|
return false;
|
|
@@ -39238,6 +39511,11 @@ function stillRunning(run) {
|
|
|
39238
39511
|
}
|
|
39239
39512
|
return true;
|
|
39240
39513
|
}
|
|
39514
|
+
function playOf(answer) {
|
|
39515
|
+
const play = readNumber(answer, "play");
|
|
39516
|
+
const editorPid = readNumber(answer, "editorPid");
|
|
39517
|
+
return play === undefined || editorPid === undefined ? null : { play, editorPid };
|
|
39518
|
+
}
|
|
39241
39519
|
function runIsUp(run, editorSays, now = Date.now()) {
|
|
39242
39520
|
if (run?.throughEditor !== true) {
|
|
39243
39521
|
return stillRunning(run);
|
|
@@ -39285,11 +39563,19 @@ function stopVerdict(stop) {
|
|
|
39285
39563
|
return {
|
|
39286
39564
|
stopped: true,
|
|
39287
39565
|
endedPid: stop.endedPid,
|
|
39288
|
-
clean: stop.
|
|
39566
|
+
clean: stop.clean,
|
|
39289
39567
|
note: "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running.",
|
|
39290
39568
|
withChildren: false
|
|
39291
39569
|
};
|
|
39292
39570
|
}
|
|
39571
|
+
if (stop.ending === "unreached") {
|
|
39572
|
+
return {
|
|
39573
|
+
stopped: false,
|
|
39574
|
+
notSignalled: stop.endedPid,
|
|
39575
|
+
note: "Nothing was ended: the editor could not be asked to stop the scene it is playing, so the game may still be running. editor_status says whether the editor is connected and playing.",
|
|
39576
|
+
withChildren: false
|
|
39577
|
+
};
|
|
39578
|
+
}
|
|
39293
39579
|
if (stop.ending === "refused") {
|
|
39294
39580
|
return {
|
|
39295
39581
|
stopped: false,
|
|
@@ -39301,13 +39587,13 @@ function stopVerdict(stop) {
|
|
|
39301
39587
|
return {
|
|
39302
39588
|
stopped: true,
|
|
39303
39589
|
endedPid: stop.endedPid,
|
|
39304
|
-
clean: stop.
|
|
39590
|
+
clean: stop.clean,
|
|
39305
39591
|
note: `${stop.throughEditor ? stop.endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."}${stop.exitSignal === null ? "" : ` It was ended by ${stop.exitSignal} and so has no exit code.`}${stop.ending === "gone" ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`}`,
|
|
39306
39592
|
withChildren: true
|
|
39307
39593
|
};
|
|
39308
39594
|
}
|
|
39309
39595
|
function endedPreviousRun(ended) {
|
|
39310
|
-
if (ended === null || ended.ending === "refused") {
|
|
39596
|
+
if (ended === null || ended.ending === "refused" || ended.ending === "unreached") {
|
|
39311
39597
|
return;
|
|
39312
39598
|
}
|
|
39313
39599
|
return ended.pid ?? true;
|
|
@@ -39389,6 +39675,9 @@ function endedToStartThis(ended) {
|
|
|
39389
39675
|
if (ended.ending === "refused") {
|
|
39390
39676
|
return ` ${which} was not ended: that pid no longer answers as the process the run was started as, so nothing was signalled. If it is still the game, it is running beside this one and you have to end it yourself; if it is not, it belongs to something else.`;
|
|
39391
39677
|
}
|
|
39678
|
+
if (ended.ending === "unreached") {
|
|
39679
|
+
return ` ${which} was not ended here: the editor could not be asked to stop it. The editor stops a play before starting the next, so it is not running beside this one if this start reached the editor.`;
|
|
39680
|
+
}
|
|
39392
39681
|
const after = ended.ending === "lingering" ? ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be running beside this one: editor_status says whether it is.` : "";
|
|
39393
39682
|
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.${after}`;
|
|
39394
39683
|
}
|
|
@@ -39514,19 +39803,58 @@ function withScanWait(outcome, scanned) {
|
|
|
39514
39803
|
if (scanned.waitedMs === 0) {
|
|
39515
39804
|
return outcome;
|
|
39516
39805
|
}
|
|
39517
|
-
const said = scanWaitAnswer(scanned);
|
|
39806
|
+
const said = scanWaitAnswer(scanned, "engine");
|
|
39518
39807
|
if (outcome.ok) {
|
|
39519
39808
|
return { ...outcome, payload: { ...outcome.payload, ...said } };
|
|
39520
39809
|
}
|
|
39521
39810
|
return said.scanNote === undefined ? outcome : { ...outcome, message: `${outcome.message} ${said.scanNote}` };
|
|
39522
39811
|
}
|
|
39523
|
-
function
|
|
39812
|
+
function notRunNote(notRun, failFast) {
|
|
39813
|
+
if (notRun <= 0) {
|
|
39814
|
+
return;
|
|
39815
|
+
}
|
|
39816
|
+
const cases = `${notRun} case${notRun === 1 ? "" : "s"}`;
|
|
39817
|
+
return failFast ? `This run stopped at the first failure in each suite it failed in, so ${cases} never ran and count as neither passed nor failed. Leave failFast out to run every case.` : `${cases} counted in ${notRun === 1 ? "its suite" : "their suites"} never ran and count as neither passed nor failed. failFast was not set, so it was not that: the suites below with notRun are the ones to read.`;
|
|
39818
|
+
}
|
|
39819
|
+
function exportAnswer(ending, asked, before, after) {
|
|
39820
|
+
const { log } = ending;
|
|
39821
|
+
const written = after !== null && after !== before;
|
|
39822
|
+
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
39823
|
+
const verdict = {
|
|
39824
|
+
exported: ending.exitCode === 0 && ending.failure === null && log.count("error") === 0 && written,
|
|
39825
|
+
...asked,
|
|
39826
|
+
exitCode: ending.exitCode,
|
|
39827
|
+
exitSignal: ending.exitSignal ?? undefined,
|
|
39828
|
+
failure: ending.failure ?? undefined,
|
|
39829
|
+
errors: log.count("error"),
|
|
39830
|
+
warnings: log.count("warning"),
|
|
39831
|
+
entries: forAnswer(problems.entries),
|
|
39832
|
+
...problems.omitted === 0 ? {} : { entriesOmitted: problems.omitted }
|
|
39833
|
+
};
|
|
39834
|
+
if (verdict.exported) {
|
|
39835
|
+
return { content: [{ type: "text", text: JSON.stringify(verdict, null, 2) }] };
|
|
39836
|
+
}
|
|
39837
|
+
const stale = !written && after !== null ? ` The ${asked.outputPath} there is from before this export, which did not write it.` : "";
|
|
39838
|
+
const why = ending.exitSignal === null ? stale : ` The engine was ${howItEnded(ending)}, so it has no exit code.${stale}`;
|
|
39839
|
+
return {
|
|
39840
|
+
content: [
|
|
39841
|
+
{
|
|
39842
|
+
type: "text",
|
|
39843
|
+
text: `Export with preset '${asked.preset}' did not produce ${asked.outputPath}.${why}`
|
|
39844
|
+
},
|
|
39845
|
+
{ type: "text", text: JSON.stringify(verdict, null, 2) }
|
|
39846
|
+
],
|
|
39847
|
+
isError: true
|
|
39848
|
+
};
|
|
39849
|
+
}
|
|
39850
|
+
function scanWaitAnswer(scanned, started = "game") {
|
|
39524
39851
|
if (scanned.waitedMs === 0) {
|
|
39525
39852
|
return {};
|
|
39526
39853
|
}
|
|
39854
|
+
const still = `The editor was still scanning the project after ${SCAN_WAIT_MS / 1000} seconds, so`;
|
|
39527
39855
|
return {
|
|
39528
39856
|
waitedForEditorScanMs: scanned.waitedMs,
|
|
39529
|
-
scanNote: scanned.stillScanning ?
|
|
39857
|
+
scanNote: !scanned.stillScanning ? undefined : started === "game" ? `${still} this game started during the scan and may have read a class cache being rewritten. If ` + 'editor_output shows "Could not find type" for a class that is declared, editor_rescan ' + "waits for the scan to finish, and a start after it reads the finished cache." : `${still} the engine answering this started during the scan and may have read a class cache being ` + "rewritten, and a class cache it wrote can be written over when the scan finishes. If " + 'engine_messages shows "Could not find type" for a class that is declared, editor_rescan waits ' + "for the scan to finish, and the same call after it reads the finished cache."
|
|
39530
39858
|
};
|
|
39531
39859
|
}
|
|
39532
39860
|
function changedSettings(before, after) {
|
|
@@ -39581,16 +39909,17 @@ function importStatuses(payload) {
|
|
|
39581
39909
|
};
|
|
39582
39910
|
});
|
|
39583
39911
|
}
|
|
39584
|
-
function escapingPropertyValue(
|
|
39585
|
-
if (typeof
|
|
39912
|
+
function escapingPropertyValue(value) {
|
|
39913
|
+
if (typeof value === "string") {
|
|
39914
|
+
return /^(res|uid):\/\//.test(value) && value.split(/[/\\]/).includes("..") ? JSON.stringify(value) : undefined;
|
|
39915
|
+
}
|
|
39916
|
+
if (typeof value !== "object" || value === null) {
|
|
39586
39917
|
return;
|
|
39587
39918
|
}
|
|
39588
|
-
for (const
|
|
39589
|
-
|
|
39590
|
-
|
|
39591
|
-
|
|
39592
|
-
if (value.split(/[/\\]/).includes("..")) {
|
|
39593
|
-
return JSON.stringify(value);
|
|
39919
|
+
for (const item of Object.values(value)) {
|
|
39920
|
+
const found = escapingPropertyValue(item);
|
|
39921
|
+
if (found !== undefined) {
|
|
39922
|
+
return found;
|
|
39594
39923
|
}
|
|
39595
39924
|
}
|
|
39596
39925
|
return;
|
|
@@ -39611,7 +39940,7 @@ function aboveTheRunner(entry) {
|
|
|
39611
39940
|
}
|
|
39612
39941
|
function elsewhereIn(projectPath, asked) {
|
|
39613
39942
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
39614
|
-
if (wanted === "" ||
|
|
39943
|
+
if (wanted === "" || existsSync17(join23(projectPath, wanted))) {
|
|
39615
39944
|
return "";
|
|
39616
39945
|
}
|
|
39617
39946
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -39625,7 +39954,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
39625
39954
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
39626
39955
|
}
|
|
39627
39956
|
function hasMainScene(projectFile) {
|
|
39628
|
-
const scene = parseProjectGodot(
|
|
39957
|
+
const scene = parseProjectGodot(readFileSync20(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
39629
39958
|
return typeof scene === "string" && scene !== "";
|
|
39630
39959
|
}
|
|
39631
39960
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -39633,13 +39962,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
39633
39962
|
return false;
|
|
39634
39963
|
}
|
|
39635
39964
|
try {
|
|
39636
|
-
return
|
|
39965
|
+
return readFileSync20(join23(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
39637
39966
|
} catch {
|
|
39638
39967
|
return false;
|
|
39639
39968
|
}
|
|
39640
39969
|
}
|
|
39641
39970
|
function editorPlaysHeadless(projectFile) {
|
|
39642
|
-
const runArgs = parseProjectGodot(
|
|
39971
|
+
const runArgs = parseProjectGodot(readFileSync20(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
39643
39972
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
39644
39973
|
}
|
|
39645
39974
|
function camelCased(params) {
|
|
@@ -39658,7 +39987,7 @@ function needsItsOwnProcess(asked) {
|
|
|
39658
39987
|
class GodotServer {
|
|
39659
39988
|
mcp;
|
|
39660
39989
|
locator = new GodotLocator;
|
|
39661
|
-
operationsScript =
|
|
39990
|
+
operationsScript = join23(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
39662
39991
|
godotBridge;
|
|
39663
39992
|
tools = buildToolDefinitions();
|
|
39664
39993
|
activeProcess = null;
|
|
@@ -40158,6 +40487,9 @@ class GodotServer {
|
|
|
40158
40487
|
const contained = this.containProjectFiles(asks);
|
|
40159
40488
|
return contained.ok ? await this.handleViaBridge(editorSide, contained.value) : contained.response;
|
|
40160
40489
|
}
|
|
40490
|
+
if (headless === "create_script") {
|
|
40491
|
+
return await this.handleCreateScript(answerable);
|
|
40492
|
+
}
|
|
40161
40493
|
if (headless !== "refresh_class_cache") {
|
|
40162
40494
|
return await this.headless(headless, answerable);
|
|
40163
40495
|
}
|
|
@@ -40328,7 +40660,7 @@ class GodotServer {
|
|
|
40328
40660
|
return await this.handleRuntimeCommand("click", {
|
|
40329
40661
|
...whichGame(args),
|
|
40330
40662
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
40331
|
-
button:
|
|
40663
|
+
button: args["button"] ?? "left",
|
|
40332
40664
|
double: readBoolean(args, "doubleClick") ?? false,
|
|
40333
40665
|
...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
|
|
40334
40666
|
...args["index"] === undefined ? {} : { index: args["index"] }
|
|
@@ -40400,8 +40732,8 @@ class GodotServer {
|
|
|
40400
40732
|
if (path === undefined) {
|
|
40401
40733
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
40402
40734
|
}
|
|
40403
|
-
const file =
|
|
40404
|
-
if (!
|
|
40735
|
+
const file = join23(path, "project.godot");
|
|
40736
|
+
if (!existsSync17(file)) {
|
|
40405
40737
|
return {
|
|
40406
40738
|
ok: false,
|
|
40407
40739
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -40611,13 +40943,30 @@ class GodotServer {
|
|
|
40611
40943
|
}
|
|
40612
40944
|
contained[key] = `res://${location.relativePath}`;
|
|
40613
40945
|
}
|
|
40614
|
-
const escaping = escapingPropertyValue(args["properties"]);
|
|
40946
|
+
const escaping = escapingPropertyValue([args["properties"], args["track"]]);
|
|
40615
40947
|
if (escaping !== undefined) {
|
|
40616
40948
|
return {
|
|
40617
40949
|
ok: false,
|
|
40618
40950
|
response: this.createErrorResponse(`The property value ${escaping} resolves outside the project directory.`, PATH_SOLUTIONS)
|
|
40619
40951
|
};
|
|
40620
40952
|
}
|
|
40953
|
+
const sources = args["sources"];
|
|
40954
|
+
if (Array.isArray(sources)) {
|
|
40955
|
+
const resolved = [];
|
|
40956
|
+
for (const source of sources) {
|
|
40957
|
+
const texture = typeof source === "object" && source !== null ? source["texture"] : undefined;
|
|
40958
|
+
if (typeof texture !== "string") {
|
|
40959
|
+
resolved.push(source);
|
|
40960
|
+
continue;
|
|
40961
|
+
}
|
|
40962
|
+
const location = resolveWithinProject(projectPath, texture);
|
|
40963
|
+
if (!location.ok) {
|
|
40964
|
+
return { ok: false, response: this.createErrorResponse(location.reason, PATH_SOLUTIONS) };
|
|
40965
|
+
}
|
|
40966
|
+
resolved.push({ ...source, texture: `res://${location.relativePath}` });
|
|
40967
|
+
}
|
|
40968
|
+
contained["sources"] = resolved;
|
|
40969
|
+
}
|
|
40621
40970
|
const pluginName = readNonEmptyString(args, "pluginName");
|
|
40622
40971
|
if (pluginName !== undefined) {
|
|
40623
40972
|
const plugin = resolveWithinProject(projectPath, `addons/${pluginName}/plugin.cfg`);
|
|
@@ -40665,6 +41014,7 @@ class GodotServer {
|
|
|
40665
41014
|
return this.answer(withScanWait({
|
|
40666
41015
|
ok: true,
|
|
40667
41016
|
messages: imported.messages,
|
|
41017
|
+
...imported.messagesOmitted === undefined ? {} : { messagesOmitted: imported.messagesOmitted },
|
|
40668
41018
|
payload: {
|
|
40669
41019
|
uidsCreated: given,
|
|
40670
41020
|
stillWithoutUid: after,
|
|
@@ -40715,16 +41065,25 @@ class GodotServer {
|
|
|
40715
41065
|
if (changed.length === 0) {
|
|
40716
41066
|
return written;
|
|
40717
41067
|
}
|
|
40718
|
-
const
|
|
41068
|
+
const adopting = {};
|
|
41069
|
+
const taken = await this.editorTakes("adopt_project_settings", { settings: changed }, adopting);
|
|
41070
|
+
if (taken["editorNote"] !== undefined) {
|
|
41071
|
+
return this.jsonTextResponse({ ...answer, ...taken });
|
|
41072
|
+
}
|
|
41073
|
+
const adopted = Array.isArray(adopting["adopted"]) ? adopting["adopted"].map(String) : [];
|
|
41074
|
+
const passedOver = changed.filter((name) => !adopted.includes(name));
|
|
40719
41075
|
return this.jsonTextResponse({
|
|
40720
41076
|
...answer,
|
|
40721
|
-
|
|
40722
|
-
...
|
|
41077
|
+
editorAdopted: adopted,
|
|
41078
|
+
...passedOver.length > 0 ? { editorPassedOver: passedOver } : {}
|
|
40723
41079
|
});
|
|
40724
41080
|
}
|
|
40725
|
-
async editorTakes(command, args) {
|
|
41081
|
+
async editorTakes(command, args, answered = {}) {
|
|
40726
41082
|
try {
|
|
40727
|
-
await this.godotBridge.invokeTool(command, args);
|
|
41083
|
+
const result = await this.godotBridge.invokeTool(command, args);
|
|
41084
|
+
if (typeof result === "object" && result !== null) {
|
|
41085
|
+
Object.assign(answered, result);
|
|
41086
|
+
}
|
|
40728
41087
|
return {};
|
|
40729
41088
|
} catch (error) {
|
|
40730
41089
|
const message = errorMessage(error);
|
|
@@ -40762,7 +41121,19 @@ class GodotServer {
|
|
|
40762
41121
|
}
|
|
40763
41122
|
const applied = await this.reimport(project.value.path, readNonEmptyString(contained.value, "resourcePath"), true, readPositiveNumber(args, "timeoutMs") ?? REIMPORT_TIMEOUT_MS);
|
|
40764
41123
|
if (!applied.ok) {
|
|
40765
|
-
|
|
41124
|
+
const listed = written.payload["updated_options"];
|
|
41125
|
+
const updated = Array.isArray(listed) ? listed.map(String) : [];
|
|
41126
|
+
const which = updated.length === 0 ? "The options were" : `${updated.join(", ")} ${updated.length === 1 ? "was" : "were"}`;
|
|
41127
|
+
return {
|
|
41128
|
+
...applied.response,
|
|
41129
|
+
content: [
|
|
41130
|
+
{
|
|
41131
|
+
type: "text",
|
|
41132
|
+
text: `${which} written to the import file, and the reimport that applies them failed, so the resource is still imported the old way until the editor's next scan or project_import reimport imports it. Why the reimport failed:`
|
|
41133
|
+
},
|
|
41134
|
+
...applied.response.content
|
|
41135
|
+
]
|
|
41136
|
+
};
|
|
40766
41137
|
}
|
|
40767
41138
|
return this.jsonTextResponse({ ...written.payload, reimport: applied.payload });
|
|
40768
41139
|
}
|
|
@@ -40780,7 +41151,14 @@ class GodotServer {
|
|
|
40780
41151
|
response: this.createErrorResponse(`${missingSource[0]} is not on disk, so there is nothing to import.`)
|
|
40781
41152
|
};
|
|
40782
41153
|
}
|
|
40783
|
-
const
|
|
41154
|
+
const never = listed.find((one) => one.status === "not_imported");
|
|
41155
|
+
if (resourcePath !== undefined && never !== undefined) {
|
|
41156
|
+
return {
|
|
41157
|
+
ok: false,
|
|
41158
|
+
response: this.createErrorResponse(`${never.path} is not something the engine imports: ${never.reason ?? "it has no importer"}.`)
|
|
41159
|
+
};
|
|
41160
|
+
}
|
|
41161
|
+
const targets = listed.filter((one) => one.status !== "missing_source" && one.status !== "not_imported" && (force || one.status !== "up_to_date")).map((one) => one.path);
|
|
40784
41162
|
if (targets.length === 0) {
|
|
40785
41163
|
return {
|
|
40786
41164
|
ok: true,
|
|
@@ -40819,9 +41197,7 @@ class GodotServer {
|
|
|
40819
41197
|
if (!imported.ok) {
|
|
40820
41198
|
return { ok: false, response: this.answer(withScanWait(imported, scanned)) };
|
|
40821
41199
|
}
|
|
40822
|
-
|
|
40823
|
-
extra["engine_messages"] = imported.messages;
|
|
40824
|
-
}
|
|
41200
|
+
Object.assign(extra, engineExtras(imported));
|
|
40825
41201
|
if (imported.extensionNote !== undefined) {
|
|
40826
41202
|
extra["extensionNote"] = imported.extensionNote;
|
|
40827
41203
|
}
|
|
@@ -40904,17 +41280,15 @@ class GodotServer {
|
|
|
40904
41280
|
return withScanWait(outcome, scanned);
|
|
40905
41281
|
}
|
|
40906
41282
|
answer(outcome) {
|
|
41283
|
+
const extras = engineExtras(outcome);
|
|
40907
41284
|
if (!outcome.ok) {
|
|
40908
41285
|
const response = this.createErrorResponse(outcome.message);
|
|
40909
|
-
if (
|
|
40910
|
-
response.content.push({
|
|
40911
|
-
type: "text",
|
|
40912
|
-
text: JSON.stringify({ engine_messages: outcome.messages }, null, 2)
|
|
40913
|
-
});
|
|
41286
|
+
if (Object.keys(extras).length > 0) {
|
|
41287
|
+
response.content.push({ type: "text", text: JSON.stringify(extras, null, 2) });
|
|
40914
41288
|
}
|
|
40915
41289
|
return response;
|
|
40916
41290
|
}
|
|
40917
|
-
return this.jsonTextResponse(
|
|
41291
|
+
return this.jsonTextResponse({ ...outcome.payload, ...extras });
|
|
40918
41292
|
}
|
|
40919
41293
|
async handleProjectInfo(args) {
|
|
40920
41294
|
const include = readStringArray(args, "include") ?? [];
|
|
@@ -40930,7 +41304,7 @@ class GodotServer {
|
|
|
40930
41304
|
if (!engine.ok) {
|
|
40931
41305
|
return engine.response;
|
|
40932
41306
|
}
|
|
40933
|
-
const application = parseProjectGodot(
|
|
41307
|
+
const application = parseProjectGodot(readFileSync20(project.value.file, "utf8"))["application"] ?? {};
|
|
40934
41308
|
const name = application["config/name"];
|
|
40935
41309
|
const mainScene = application["run/main_scene"];
|
|
40936
41310
|
const info = {
|
|
@@ -40947,7 +41321,7 @@ class GodotServer {
|
|
|
40947
41321
|
continue;
|
|
40948
41322
|
}
|
|
40949
41323
|
const outcome = await this.operation(spec.operation, spec.params(args, detailed), project.value.path);
|
|
40950
|
-
info[section] = outcome.ok ?
|
|
41324
|
+
info[section] = outcome.ok ? { ...outcome.payload, ...engineExtras(outcome) } : { error: outcome.message, ...engineExtras(outcome) };
|
|
40951
41325
|
}
|
|
40952
41326
|
return this.jsonTextResponse(info);
|
|
40953
41327
|
}
|
|
@@ -40991,18 +41365,18 @@ class GodotServer {
|
|
|
40991
41365
|
if (!engine.ok) {
|
|
40992
41366
|
return engine.response;
|
|
40993
41367
|
}
|
|
40994
|
-
const folder =
|
|
41368
|
+
const folder = dirname16(output.absolutePath);
|
|
40995
41369
|
try {
|
|
40996
41370
|
mkdirSync12(folder, { recursive: true });
|
|
40997
41371
|
} catch (error) {
|
|
40998
|
-
return this.createErrorResponse(`Cannot create ${
|
|
41372
|
+
return this.createErrorResponse(`Cannot create ${dirname16(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
40999
41373
|
}
|
|
41000
41374
|
const debug = readBoolean(args, "debug") ?? false;
|
|
41001
41375
|
const exportLogs = scratchDirectory("export");
|
|
41002
41376
|
const exportArgs = [
|
|
41003
41377
|
"--headless",
|
|
41004
41378
|
"--log-file",
|
|
41005
|
-
|
|
41379
|
+
join23(exportLogs, "engine.log"),
|
|
41006
41380
|
"--path",
|
|
41007
41381
|
project.value.path,
|
|
41008
41382
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -41010,55 +41384,18 @@ class GodotServer {
|
|
|
41010
41384
|
output.absolutePath
|
|
41011
41385
|
];
|
|
41012
41386
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
41013
|
-
const
|
|
41014
|
-
|
|
41387
|
+
const writtenAt = () => existsSync17(output.absolutePath) ? statSync8(output.absolutePath).mtimeMs : null;
|
|
41388
|
+
const before = writtenAt();
|
|
41389
|
+
let ending;
|
|
41015
41390
|
try {
|
|
41016
|
-
|
|
41017
|
-
timeout: 300000,
|
|
41018
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
41019
|
-
signal: callSignal()
|
|
41020
|
-
});
|
|
41021
|
-
log.append("stdout", stdout);
|
|
41022
|
-
log.append("stderr", stderr);
|
|
41023
|
-
} catch (error) {
|
|
41024
|
-
if (!(error instanceof Error && ("stdout" in error) && ("stderr" in error))) {
|
|
41025
|
-
return this.createErrorResponse(`Export could not be run: ${errorMessage(error)}`);
|
|
41026
|
-
}
|
|
41027
|
-
const failed = error;
|
|
41028
|
-
log.append("stdout", failed.stdout);
|
|
41029
|
-
log.append("stderr", failed.stderr);
|
|
41030
|
-
ending = endingOf(failed);
|
|
41391
|
+
ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
|
|
41031
41392
|
} finally {
|
|
41032
41393
|
discard(exportLogs);
|
|
41033
41394
|
}
|
|
41034
|
-
|
|
41035
|
-
|
|
41036
|
-
const verdict = {
|
|
41037
|
-
exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
41038
|
-
preset,
|
|
41039
|
-
outputPath: output.relativePath,
|
|
41040
|
-
debug,
|
|
41041
|
-
exitCode: ending.exitCode,
|
|
41042
|
-
exitSignal: ending.exitSignal ?? undefined,
|
|
41043
|
-
failure: ending.failure ?? undefined,
|
|
41044
|
-
errors: log.count("error"),
|
|
41045
|
-
warnings: log.count("warning"),
|
|
41046
|
-
entries: forAnswer(problems.entries)
|
|
41047
|
-
};
|
|
41048
|
-
if (!verdict.exported) {
|
|
41049
|
-
const why = ending.exitSignal === null ? ending.failure === null ? "" : ` The engine was ended by Node (${ending.failure}), so it has no exit code.` : ` The engine was ended by ${ending.exitSignal}, so it has no exit code: the export ran past its five minutes or the call was cancelled.`;
|
|
41050
|
-
return {
|
|
41051
|
-
content: [
|
|
41052
|
-
{
|
|
41053
|
-
type: "text",
|
|
41054
|
-
text: `Export with preset '${preset}' did not produce ${output.relativePath}.${why}`
|
|
41055
|
-
},
|
|
41056
|
-
{ type: "text", text: JSON.stringify(verdict, null, 2) }
|
|
41057
|
-
],
|
|
41058
|
-
isError: true
|
|
41059
|
-
};
|
|
41395
|
+
if (ending.exitSignal === null && ending.exitCode === null) {
|
|
41396
|
+
return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
|
|
41060
41397
|
}
|
|
41061
|
-
return
|
|
41398
|
+
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt());
|
|
41062
41399
|
}
|
|
41063
41400
|
async handleRunTests(args) {
|
|
41064
41401
|
const project = this.project(args);
|
|
@@ -41070,7 +41407,7 @@ class GodotServer {
|
|
|
41070
41407
|
return contained.response;
|
|
41071
41408
|
}
|
|
41072
41409
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
41073
|
-
if (!
|
|
41410
|
+
if (!existsSync17(join23(project.value.path, runner))) {
|
|
41074
41411
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
41075
41412
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
41076
41413
|
]);
|
|
@@ -41136,21 +41473,22 @@ class GodotServer {
|
|
|
41136
41473
|
if (timedOut && run.process.pid !== undefined) {
|
|
41137
41474
|
await untilGone([run.process.pid]);
|
|
41138
41475
|
}
|
|
41139
|
-
|
|
41476
|
+
await run.settled;
|
|
41477
|
+
const reportsDir = join23(project.value.path, ".godot", ...ours.split("/"));
|
|
41140
41478
|
let report = null;
|
|
41141
41479
|
let reportProblem = null;
|
|
41142
41480
|
try {
|
|
41143
|
-
const written =
|
|
41481
|
+
const written = existsSync17(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
41144
41482
|
const newest = written.at(-1);
|
|
41145
41483
|
if (newest !== undefined) {
|
|
41146
|
-
report = parseJUnit(
|
|
41484
|
+
report = parseJUnit(readFileSync20(join23(reportsDir, newest, "results.xml"), "utf8"));
|
|
41147
41485
|
}
|
|
41148
41486
|
} catch (error) {
|
|
41149
41487
|
reportProblem = errorMessage(error);
|
|
41150
41488
|
} finally {
|
|
41151
41489
|
discard(reportsDir, userData);
|
|
41152
41490
|
try {
|
|
41153
|
-
rmdirSync2(
|
|
41491
|
+
rmdirSync2(dirname16(reportsDir));
|
|
41154
41492
|
} catch {}
|
|
41155
41493
|
}
|
|
41156
41494
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
@@ -41163,7 +41501,11 @@ class GodotServer {
|
|
|
41163
41501
|
105: "script errors"
|
|
41164
41502
|
};
|
|
41165
41503
|
const exitCode = run.exitCode;
|
|
41166
|
-
const printed = run.log.select({
|
|
41504
|
+
const printed = run.log.select({
|
|
41505
|
+
severity: "info",
|
|
41506
|
+
sinceLastCall: false,
|
|
41507
|
+
limit: Number.POSITIVE_INFINITY
|
|
41508
|
+
}).entries;
|
|
41167
41509
|
const said = printed.map((entry) => entry.text);
|
|
41168
41510
|
const nothingRan = timedOut ? null : whyNoReport(said, asked);
|
|
41169
41511
|
const cutShort = timedOut ? timedOutVerdict(timeoutMs, silentForMs) : null;
|
|
@@ -41179,7 +41521,7 @@ class GodotServer {
|
|
|
41179
41521
|
...echoed.omitted > 0 ? { printedOmitted: echoed.omitted } : {}
|
|
41180
41522
|
};
|
|
41181
41523
|
if (report === null) {
|
|
41182
|
-
const note = nothingRan !== null ? `No tests ran: ${verdict}.${elsewhereIn(project.value.path, asked)}` : scriptErrors.length > 0 ? scriptErrorsNote(scriptErrors) : `The test run
|
|
41524
|
+
const note = nothingRan !== null ? `No tests ran: ${verdict}.${elsewhereIn(project.value.path, asked)}` : scriptErrors.length > 0 ? scriptErrorsNote(scriptErrors) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${verdict}.` : `The test run wrote no report (${verdict}).`;
|
|
41183
41525
|
return {
|
|
41184
41526
|
content: [
|
|
41185
41527
|
{ type: "text", text: note },
|
|
@@ -41216,10 +41558,21 @@ class GodotServer {
|
|
|
41216
41558
|
orphans: suite.orphans,
|
|
41217
41559
|
message: `${suite.orphans} node${suite.orphans === 1 ? "" : "s"} were still in the tree when this suite finished. A node built in a test and neither freed nor added to the tree is the usual cause; free it, or add it with auto_free().`
|
|
41218
41560
|
}));
|
|
41219
|
-
const unclean = report.suites.filter((suite) => suite.failures > 0 || suite.errors > 0 || suite.skipped > 0 || orphansIn(suite.path) > 0);
|
|
41561
|
+
const unclean = report.suites.filter((suite) => suite.failures > 0 || suite.errors > 0 || suite.hookFailures > 0 || suite.hookErrors > 0 || suite.skipped > 0 || orphansIn(suite.path) > 0);
|
|
41562
|
+
const hooksPrinted = hookFailuresPrinted(said);
|
|
41563
|
+
const hookFailures = report.suites.filter((suite) => suite.hookFailures + suite.hookErrors > 0).flatMap((suite) => {
|
|
41564
|
+
const printedHere = hooksPrinted.filter((hook) => hook.path === suite.path);
|
|
41565
|
+
return printedHere.length > 0 ? printedHere.map((hook) => ({ suite: suite.name, ...hook })) : [
|
|
41566
|
+
{
|
|
41567
|
+
suite: suite.name,
|
|
41568
|
+
path: suite.path,
|
|
41569
|
+
message: "gdUnit4 counted a failure in this suite that none of its cases carry, which is what a failed before() or after() leaves, and printed no line saying which."
|
|
41570
|
+
}
|
|
41571
|
+
];
|
|
41572
|
+
});
|
|
41220
41573
|
const notRun = report.suites.reduce((sum, suite) => sum + Math.max(0, suite.discovered - suite.tests), 0);
|
|
41221
41574
|
return this.jsonTextResponse({
|
|
41222
|
-
passed: !timedOut && exitCode === 0 && report.failures === 0 && report.errors === 0,
|
|
41575
|
+
passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0,
|
|
41223
41576
|
verdict,
|
|
41224
41577
|
...timedOut ? { timedOut, hung, silentForMs } : {},
|
|
41225
41578
|
exitCode,
|
|
@@ -41230,13 +41583,14 @@ class GodotServer {
|
|
|
41230
41583
|
skipped: report.skipped,
|
|
41231
41584
|
time: report.time,
|
|
41232
41585
|
failed,
|
|
41586
|
+
hookFailures: hookFailures.length > 0 ? hookFailures : undefined,
|
|
41233
41587
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
41234
41588
|
orphans: orphans.total > 0 ? orphans.total : undefined,
|
|
41235
41589
|
notRun: notRun > 0 ? notRun : undefined,
|
|
41236
41590
|
...printedLines,
|
|
41237
41591
|
savesNote,
|
|
41238
41592
|
...scanned,
|
|
41239
|
-
note: verdict.startsWith("warnings") && warnings.length === 0 ? "gdUnit4 exits 101 for orphan nodes when nothing failed, and this run printed no count of them: orphan reporting may be off in the project settings." : notRun
|
|
41593
|
+
note: verdict.startsWith("warnings") && warnings.length === 0 ? "gdUnit4 exits 101 for orphan nodes when nothing failed, and this run printed no count of them: orphan reporting may be off in the project settings." : notRunNote(notRun, readBoolean(args, "failFast") === true),
|
|
41240
41594
|
suites: unclean.map((suite) => ({
|
|
41241
41595
|
name: suite.name,
|
|
41242
41596
|
path: suite.path,
|
|
@@ -41244,6 +41598,7 @@ class GodotServer {
|
|
|
41244
41598
|
notRun: suite.discovered > suite.tests ? suite.discovered - suite.tests : undefined,
|
|
41245
41599
|
failures: suite.failures,
|
|
41246
41600
|
errors: suite.errors,
|
|
41601
|
+
hookFailures: suite.hookFailures + suite.hookErrors > 0 ? suite.hookFailures + suite.hookErrors : undefined,
|
|
41247
41602
|
skipped: suite.skipped,
|
|
41248
41603
|
orphans: orphansIn(suite.path) > 0 ? orphansIn(suite.path) : undefined,
|
|
41249
41604
|
time: suite.time
|
|
@@ -41257,7 +41612,7 @@ class GodotServer {
|
|
|
41257
41612
|
}
|
|
41258
41613
|
async godotVersion(godotPath) {
|
|
41259
41614
|
try {
|
|
41260
|
-
const { stdout } = await
|
|
41615
|
+
const { stdout } = await run4(godotPath, ["--version"], { timeout: 1e4 });
|
|
41261
41616
|
this.lastGodotVersion = stdout.trim();
|
|
41262
41617
|
return this.lastGodotVersion;
|
|
41263
41618
|
} catch (error) {
|
|
@@ -41269,8 +41624,18 @@ class GodotServer {
|
|
|
41269
41624
|
const answer = await this.handleLSP("lsp_get_diagnostics", args);
|
|
41270
41625
|
const payload = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
|
|
41271
41626
|
if (payload["error"] !== undefined) {
|
|
41272
|
-
const reason = payload["error"];
|
|
41273
|
-
|
|
41627
|
+
const reason = typeof payload["error"] === "string" ? payload["error"] : JSON.stringify(payload["error"]);
|
|
41628
|
+
if (payload["refusedArguments"] === true) {
|
|
41629
|
+
return this.createErrorResponse(reason);
|
|
41630
|
+
}
|
|
41631
|
+
if (typeof payload["serves"] === "string") {
|
|
41632
|
+
return this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
|
|
41633
|
+
"editor_status names the editor on the bridge and the port it says it serves",
|
|
41634
|
+
"editor_launch opens this project's own editor, on a language server port of its own",
|
|
41635
|
+
"GDHARNESS_LSP_PORT points this server at the right one"
|
|
41636
|
+
]);
|
|
41637
|
+
}
|
|
41638
|
+
return this.createErrorResponse(`Diagnostics unavailable: ${reason}`, [
|
|
41274
41639
|
`Ensure the Godot editor is running with its language server enabled, on port ${this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT)}`,
|
|
41275
41640
|
"GDHARNESS_LSP_PORT points this server at another one"
|
|
41276
41641
|
]);
|
|
@@ -41301,11 +41666,11 @@ class GodotServer {
|
|
|
41301
41666
|
const cached = cachedClasses(projectPath);
|
|
41302
41667
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
41303
41668
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
41304
|
-
if (!contained.ok || !
|
|
41669
|
+
if (!contained.ok || !existsSync17(contained.absolutePath)) {
|
|
41305
41670
|
return null;
|
|
41306
41671
|
}
|
|
41307
41672
|
try {
|
|
41308
|
-
return
|
|
41673
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
41309
41674
|
} catch {
|
|
41310
41675
|
return null;
|
|
41311
41676
|
}
|
|
@@ -41361,11 +41726,11 @@ class GodotServer {
|
|
|
41361
41726
|
}
|
|
41362
41727
|
sourceOfClass(projectPath, resourcePath) {
|
|
41363
41728
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
41364
|
-
if (!contained.ok || !
|
|
41729
|
+
if (!contained.ok || !existsSync17(contained.absolutePath)) {
|
|
41365
41730
|
return null;
|
|
41366
41731
|
}
|
|
41367
41732
|
try {
|
|
41368
|
-
return
|
|
41733
|
+
return readFileSync20(contained.absolutePath, "utf8");
|
|
41369
41734
|
} catch {
|
|
41370
41735
|
return null;
|
|
41371
41736
|
}
|
|
@@ -41553,7 +41918,8 @@ class GodotServer {
|
|
|
41553
41918
|
return {
|
|
41554
41919
|
playing: readBoolean(asParams(answer), "playing") ?? false,
|
|
41555
41920
|
scene: readString(asParams(answer), "scenePath") ?? "",
|
|
41556
|
-
debugPort: readNumber(asParams(answer), "debugPort")
|
|
41921
|
+
debugPort: readNumber(asParams(answer), "debugPort"),
|
|
41922
|
+
play: playOf(asParams(answer))
|
|
41557
41923
|
};
|
|
41558
41924
|
} catch {
|
|
41559
41925
|
return null;
|
|
@@ -41649,7 +42015,7 @@ class GodotServer {
|
|
|
41649
42015
|
return null;
|
|
41650
42016
|
}
|
|
41651
42017
|
try {
|
|
41652
|
-
return settingKeys(
|
|
42018
|
+
return settingKeys(readFileSync20(join23(projectPath, "project.godot"), "utf8"));
|
|
41653
42019
|
} catch {
|
|
41654
42020
|
return null;
|
|
41655
42021
|
}
|
|
@@ -41812,6 +42178,13 @@ class GodotServer {
|
|
|
41812
42178
|
if (!project.ok) {
|
|
41813
42179
|
return project.response;
|
|
41814
42180
|
}
|
|
42181
|
+
const open = this.godotBridge.isConnected() ? this.godotBridge.getStatus().projectPath : undefined;
|
|
42182
|
+
if (op !== "check" && open !== undefined && open !== "" && !isSameDirectory(open, project.value.path)) {
|
|
42183
|
+
return this.createErrorResponse(`This start names ${project.value.path}, and the editor on this bridge has ${open} open.`, [
|
|
42184
|
+
"editor_status names the project this server's editor is showing",
|
|
42185
|
+
"One server serves one editor: start a second server for the other project"
|
|
42186
|
+
]);
|
|
42187
|
+
}
|
|
41815
42188
|
const scene = readNonEmptyString(args, "scene");
|
|
41816
42189
|
const sceneToRun = scene !== undefined ? resolveWithinProject(project.value.path, scene) : null;
|
|
41817
42190
|
if (sceneToRun && !sceneToRun.ok) {
|
|
@@ -41906,7 +42279,7 @@ class GodotServer {
|
|
|
41906
42279
|
}
|
|
41907
42280
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
41908
42281
|
const budgetMs = budget.ms;
|
|
41909
|
-
if (!
|
|
42282
|
+
if (!existsSync17(join23(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
41910
42283
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
41911
42284
|
}
|
|
41912
42285
|
let asking = null;
|
|
@@ -41915,7 +42288,7 @@ class GodotServer {
|
|
|
41915
42288
|
let editorAsks = 0;
|
|
41916
42289
|
const waitBegan = Date.now();
|
|
41917
42290
|
const lastSaid = () => editorSaysGoing ? "playing" : "not playing";
|
|
41918
|
-
const
|
|
42291
|
+
const fresh = await announcedSince(projectPath, before, {
|
|
41919
42292
|
budgetMs,
|
|
41920
42293
|
accept: (one) => this.isTheGameOf(this.activeProcess, one, projectPath),
|
|
41921
42294
|
giveUp: () => {
|
|
@@ -41939,11 +42312,19 @@ class GodotServer {
|
|
|
41939
42312
|
}
|
|
41940
42313
|
});
|
|
41941
42314
|
const asked = editorAsks === 0 ? "editor not asked" : `editor asked ${editorAsks === 1 ? "once" : `${editorAsks} times`}, last said ${lastSaid()}`;
|
|
41942
|
-
this.logDebug(`announce wait ended after ${Date.now() - waitBegan}ms: ${
|
|
42315
|
+
this.logDebug(`announce wait ended after ${Date.now() - waitBegan}ms: ${fresh.length === 0 ? "nothing announced" : `pid ${fresh.map((one) => one.pid).join(", ")} announced`}, ${asked}`);
|
|
41943
42316
|
const going = await this.currentRun();
|
|
42317
|
+
const endpoint = await this.theRunsAmong(going, fresh);
|
|
41944
42318
|
if (endpoint !== null && going !== null && going.announcedPid === undefined) {
|
|
41945
42319
|
this.tie(going, endpoint);
|
|
41946
42320
|
}
|
|
42321
|
+
if (endpoint === null && fresh.length > 1) {
|
|
42322
|
+
return {
|
|
42323
|
+
listening: true,
|
|
42324
|
+
pids: fresh.map((one) => one.pid),
|
|
42325
|
+
note: `${fresh.length} games of this project announced at once and none could be told from the process tree as this run's own game rather than one it started, so none is taken as its runtime. editor_status lists them under runtimes.`
|
|
42326
|
+
};
|
|
42327
|
+
}
|
|
41947
42328
|
return runtimeVerdict(endpoint, {
|
|
41948
42329
|
addon: true,
|
|
41949
42330
|
budgetMs,
|
|
@@ -42027,7 +42408,13 @@ class GodotServer {
|
|
|
42027
42408
|
};
|
|
42028
42409
|
this.activeProcess = played;
|
|
42029
42410
|
this.watchThePlayedConsole();
|
|
42030
|
-
|
|
42411
|
+
const which = playOf(playAnswer);
|
|
42412
|
+
writeEditorRunNote({
|
|
42413
|
+
projectPath,
|
|
42414
|
+
transcript: transcript.path,
|
|
42415
|
+
startedAt,
|
|
42416
|
+
...which === null ? {} : { play: which }
|
|
42417
|
+
});
|
|
42031
42418
|
sweepTranscripts();
|
|
42032
42419
|
return this.jsonTextResponse({
|
|
42033
42420
|
started: true,
|
|
@@ -42142,7 +42529,7 @@ class GodotServer {
|
|
|
42142
42529
|
return 0;
|
|
42143
42530
|
}
|
|
42144
42531
|
try {
|
|
42145
|
-
return reportAlreadyInTranscript(
|
|
42532
|
+
return reportAlreadyInTranscript(readFileSync20(game.transcript), readFileSync20(report));
|
|
42146
42533
|
} catch (error) {
|
|
42147
42534
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
42148
42535
|
return 0;
|
|
@@ -42156,6 +42543,18 @@ class GodotServer {
|
|
|
42156
42543
|
game.brokeOn = halt.text;
|
|
42157
42544
|
game.log.record("error", halt.text);
|
|
42158
42545
|
}
|
|
42546
|
+
async untilTheEditorStopsPlaying(withinMs = STOP_WAIT_MS) {
|
|
42547
|
+
const deadline = Date.now() + withinMs;
|
|
42548
|
+
for (;; ) {
|
|
42549
|
+
if ((await this.editorPlayingState())?.playing === false) {
|
|
42550
|
+
return true;
|
|
42551
|
+
}
|
|
42552
|
+
if (Date.now() >= deadline) {
|
|
42553
|
+
return false;
|
|
42554
|
+
}
|
|
42555
|
+
await delay3(100);
|
|
42556
|
+
}
|
|
42557
|
+
}
|
|
42159
42558
|
async endActiveGame(reason, going) {
|
|
42160
42559
|
const running = this.activeProcess;
|
|
42161
42560
|
const project = running?.projectPath ?? this.ourProject();
|
|
@@ -42174,8 +42573,16 @@ class GodotServer {
|
|
|
42174
42573
|
running.endedHere = reason;
|
|
42175
42574
|
const announced = this.announcedPidOf(running);
|
|
42176
42575
|
if (running.throughEditor) {
|
|
42177
|
-
await this.handleViaBridge("stop_playing", {});
|
|
42178
|
-
|
|
42576
|
+
const asked = await this.handleViaBridge("stop_playing", {});
|
|
42577
|
+
if (asked.isError === true) {
|
|
42578
|
+
running.endedHere = null;
|
|
42579
|
+
running.log.record("warning", `This run was not ended here: the editor could not be asked to stop it (${asked.content[0]?.text ?? "no reason given"}).`);
|
|
42580
|
+
return "unreached";
|
|
42581
|
+
}
|
|
42582
|
+
if (announced === undefined) {
|
|
42583
|
+
return await this.untilTheEditorStopsPlaying() ? "gone" : "lingering";
|
|
42584
|
+
}
|
|
42585
|
+
return await untilGone([announced]) ? "gone" : "lingering";
|
|
42179
42586
|
}
|
|
42180
42587
|
if (running.pid === null) {
|
|
42181
42588
|
return "gone";
|
|
@@ -42215,7 +42622,7 @@ class GodotServer {
|
|
|
42215
42622
|
return gone ? "gone" : "lingering";
|
|
42216
42623
|
}
|
|
42217
42624
|
spawnGame(godotPath, cmdArgs, env) {
|
|
42218
|
-
const child =
|
|
42625
|
+
const child = spawn4(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
42219
42626
|
const cancelled = callSignal();
|
|
42220
42627
|
if (cancelled !== undefined) {
|
|
42221
42628
|
const end = () => {
|
|
@@ -42255,7 +42662,6 @@ class GodotServer {
|
|
|
42255
42662
|
});
|
|
42256
42663
|
child.on("exit", (code, signal) => {
|
|
42257
42664
|
this.logDebug(`Godot process exited with code ${code ?? "none"}, signal ${signal ?? "none"}`);
|
|
42258
|
-
log.finish();
|
|
42259
42665
|
started.exitCode = code;
|
|
42260
42666
|
started.exitSignal = code === null ? signal : null;
|
|
42261
42667
|
});
|
|
@@ -42265,7 +42671,28 @@ class GodotServer {
|
|
|
42265
42671
|
`);
|
|
42266
42672
|
started.exitCode = -1;
|
|
42267
42673
|
});
|
|
42268
|
-
|
|
42674
|
+
const settled = new Promise((resolve) => {
|
|
42675
|
+
let open = 2;
|
|
42676
|
+
const done = () => {
|
|
42677
|
+
log.finish();
|
|
42678
|
+
resolve();
|
|
42679
|
+
};
|
|
42680
|
+
const closed = () => {
|
|
42681
|
+
open -= 1;
|
|
42682
|
+
if (open === 0) {
|
|
42683
|
+
done();
|
|
42684
|
+
}
|
|
42685
|
+
};
|
|
42686
|
+
child.stdout.once("close", closed);
|
|
42687
|
+
child.stderr.once("close", closed);
|
|
42688
|
+
child.once("exit", () => {
|
|
42689
|
+
setTimeout(done, STREAMS_GRACE_MS).unref();
|
|
42690
|
+
});
|
|
42691
|
+
child.once("error", () => {
|
|
42692
|
+
done();
|
|
42693
|
+
});
|
|
42694
|
+
});
|
|
42695
|
+
return Object.assign(started, { settled });
|
|
42269
42696
|
}
|
|
42270
42697
|
async launchKeptGame(godotPath, cmdArgs, projectPath, env, desktop) {
|
|
42271
42698
|
const startedAt = Date.now();
|
|
@@ -42347,7 +42774,8 @@ class GodotServer {
|
|
|
42347
42774
|
this.logDebug(`Picked up an editor-played run without its adapter: ${errorMessage(error)}`);
|
|
42348
42775
|
}
|
|
42349
42776
|
const project = this.godotBridge.getStatus().projectPath ?? null;
|
|
42350
|
-
const
|
|
42777
|
+
const recorded = project === null ? null : readEditorRunNote(project);
|
|
42778
|
+
const note = recorded !== null && noteIsAbout(recorded, playing.play) ? recorded : null;
|
|
42351
42779
|
const picked = {
|
|
42352
42780
|
pid: null,
|
|
42353
42781
|
log: new GameLog,
|
|
@@ -42404,6 +42832,31 @@ class GodotServer {
|
|
|
42404
42832
|
const its = await this.tiedThroughTheTree(run, running);
|
|
42405
42833
|
return its !== undefined && running.some((one) => one.pid === its) ? its : undefined;
|
|
42406
42834
|
}
|
|
42835
|
+
async theRunsAmong(run, fresh) {
|
|
42836
|
+
if (fresh.length === 1) {
|
|
42837
|
+
return fresh[0] ?? null;
|
|
42838
|
+
}
|
|
42839
|
+
if (fresh.length === 0 || run === null) {
|
|
42840
|
+
return null;
|
|
42841
|
+
}
|
|
42842
|
+
const pid = run.announcedPid ?? await this.tiedThroughTheTree(run, fresh);
|
|
42843
|
+
return fresh.find((one) => one.pid === pid) ?? null;
|
|
42844
|
+
}
|
|
42845
|
+
async tieThePlayedGame(run) {
|
|
42846
|
+
if (!run.throughEditor || run.errorReport !== undefined || run.announcedPid !== undefined) {
|
|
42847
|
+
return;
|
|
42848
|
+
}
|
|
42849
|
+
const announced = runtimesAnnounced().running;
|
|
42850
|
+
if (this.announcedPidOf(run, announced) !== undefined || !stillRunning(run)) {
|
|
42851
|
+
return;
|
|
42852
|
+
}
|
|
42853
|
+
const among = this.announcedOfTheRunsProject(run, announced).map((one) => one.pid).sort((one, other) => one - other).join(",");
|
|
42854
|
+
if (among === "" || run.treeReadFor === among) {
|
|
42855
|
+
return;
|
|
42856
|
+
}
|
|
42857
|
+
run.treeReadFor = among;
|
|
42858
|
+
await this.tiedThroughTheTree(run, announced);
|
|
42859
|
+
}
|
|
42407
42860
|
async tiedThroughTheTree(run, announced) {
|
|
42408
42861
|
if (run.announcedPid !== undefined) {
|
|
42409
42862
|
return run.announcedPid;
|
|
@@ -42604,6 +43057,7 @@ class GodotServer {
|
|
|
42604
43057
|
resolve(false);
|
|
42605
43058
|
});
|
|
42606
43059
|
});
|
|
43060
|
+
await boot.settled;
|
|
42607
43061
|
const errors = boot.log.count("error");
|
|
42608
43062
|
const warnings = boot.log.count("warning");
|
|
42609
43063
|
return this.jsonTextResponse({
|
|
@@ -42745,6 +43199,7 @@ class GodotServer {
|
|
|
42745
43199
|
if (!run) {
|
|
42746
43200
|
return this.createErrorResponse(this.nothingOfOursIsRunning());
|
|
42747
43201
|
}
|
|
43202
|
+
await this.tieThePlayedGame(run);
|
|
42748
43203
|
this.drainEditorOutput(run);
|
|
42749
43204
|
this.drainTranscript(run);
|
|
42750
43205
|
const going = await this.runStillGoing(run);
|
|
@@ -42780,7 +43235,7 @@ class GodotServer {
|
|
|
42780
43235
|
notes.push(endedWithoutACode({
|
|
42781
43236
|
throughEditor: run.throughEditor,
|
|
42782
43237
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
42783
|
-
couldAnnounce: run.projectPath !== null &&
|
|
43238
|
+
couldAnnounce: run.projectPath !== null && existsSync17(join23(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
42784
43239
|
endedHere: typeof run.endedHere === "string",
|
|
42785
43240
|
pickedUp: run.pickedUp === true
|
|
42786
43241
|
}));
|
|
@@ -42788,6 +43243,9 @@ class GodotServer {
|
|
|
42788
43243
|
if (run.pickedUpPlaying === true) {
|
|
42789
43244
|
notes.push(run.transcript !== null ? "The editor was already playing this when this server reached it, and what it printed before that has been read back from its transcript. It is the run the editor is holding now, which is the one the debug_* tools answer for." : run.consoleFromReport === true ? "The editor was already playing this when this server reached it, and the server that started it left no transcript, so what is below was read back from the file the game's runtime writes its console to, which begins where the runtime started: the engine's own start-up before that is only in the editor. It is the run the editor is holding now, which is the one the debug_* tools answer for." : "The editor was already playing this when this server reached it, and the server that started it left no transcript, so this is not the run from its start: what it printed before that is only in the editor. It is the run the editor is holding now, which is the one the debug_* tools answer for.");
|
|
42790
43245
|
}
|
|
43246
|
+
if (errorsUnread(run)) {
|
|
43247
|
+
notes.push("The editor plays this run and no report from its game's runtime addon has been found, so what the game raised with push_error and the engine's own errors are not counted here: the editor's debugger relays what a game prints and not what it reports. errors counts what was heard, and clean is left out rather than said. A game with the runtime addon writes that report once it announces itself.");
|
|
43248
|
+
}
|
|
42791
43249
|
if (run.consoleLost === true) {
|
|
42792
43250
|
const back = this.dapClient?.isConnected() === true;
|
|
42793
43251
|
notes.push(`The debug adapter this run's console arrives over went away while the run was going, so what is below has a hole in it and the counts are of what was heard rather than of what was printed. ${back ? "It is connected again, and the line marking where is in the log." : "It is still gone, so nothing further will arrive here."} The project's own user://logs/godot.log has the rest, and "no entries" here means "not heard" rather than a quiet run.`);
|
|
@@ -42803,8 +43261,9 @@ class GodotServer {
|
|
|
42803
43261
|
pid: run.pid ?? announced ?? run.announcedPid ?? null,
|
|
42804
43262
|
errors: run.log.count("error"),
|
|
42805
43263
|
warnings: run.log.count("warning"),
|
|
42806
|
-
clean: run
|
|
43264
|
+
clean: cleanVerdict(run),
|
|
42807
43265
|
consoleLost: run.consoleLost === true ? true : undefined,
|
|
43266
|
+
errorsUnread: errorsUnread(run) ? true : undefined,
|
|
42808
43267
|
...hold,
|
|
42809
43268
|
endedUnwatched: run.endedUnwatched === true ? true : undefined,
|
|
42810
43269
|
endedBy: stillRunning(run) ? undefined : run.endedHere ?? null,
|
|
@@ -42839,6 +43298,7 @@ class GodotServer {
|
|
|
42839
43298
|
if (elsewhere !== null) {
|
|
42840
43299
|
return elsewhere;
|
|
42841
43300
|
}
|
|
43301
|
+
await this.tieThePlayedGame(stopped);
|
|
42842
43302
|
this.drainEditorOutput(stopped);
|
|
42843
43303
|
this.drainTranscript(stopped);
|
|
42844
43304
|
const wasRunning = await this.runStillGoing(stopped);
|
|
@@ -42847,7 +43307,7 @@ class GodotServer {
|
|
|
42847
43307
|
const children = readBoolean(args, "andChildren") === true && wasRunning ? await this.whatTheRunStarted(stopped) : null;
|
|
42848
43308
|
this.logDebug("Stopping the running game");
|
|
42849
43309
|
const ending = await this.endActiveGame("editor_run stop", wasRunning);
|
|
42850
|
-
const refused = ending === "refused";
|
|
43310
|
+
const refused = ending === "refused" || ending === "unreached";
|
|
42851
43311
|
const ended = children === null || refused ? null : endChildrenAmong(children);
|
|
42852
43312
|
if (wasRunning && !refused) {
|
|
42853
43313
|
for (const pid of new Set([endedPid, announcedPid])) {
|
|
@@ -42862,7 +43322,7 @@ class GodotServer {
|
|
|
42862
43322
|
endedPid,
|
|
42863
43323
|
throughEditor: stopped.throughEditor,
|
|
42864
43324
|
exitSignal: stopped.exitSignal,
|
|
42865
|
-
|
|
43325
|
+
clean: cleanVerdict(stopped)
|
|
42866
43326
|
});
|
|
42867
43327
|
return this.jsonTextResponse({
|
|
42868
43328
|
stopped: verdict.stopped,
|
|
@@ -42876,6 +43336,7 @@ class GodotServer {
|
|
|
42876
43336
|
errors: stopped.log.count("error"),
|
|
42877
43337
|
warnings: stopped.log.count("warning"),
|
|
42878
43338
|
clean: verdict.clean,
|
|
43339
|
+
errorsUnread: errorsUnread(stopped) ? true : undefined,
|
|
42879
43340
|
note: verdict.withChildren ? `${verdict.note} ${aboutTheChildren(ended, stopped.throughEditor)}` : verdict.note,
|
|
42880
43341
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
42881
43342
|
});
|
|
@@ -43088,7 +43549,12 @@ class GodotServer {
|
|
|
43088
43549
|
const stillAtMissingPaths = atMissingPaths.filter((name) => !dropped.includes(name));
|
|
43089
43550
|
const reloading = readNonEmptyString(args, "reloadScript");
|
|
43090
43551
|
let reloaded = {};
|
|
43091
|
-
|
|
43552
|
+
const reloadingSource = reloading === undefined || projectPath === "" ? null : this.sourceOfClass(projectPath, reloading);
|
|
43553
|
+
if (reloading !== undefined && reloadingSource !== null && isToolScript(reloadingSource)) {
|
|
43554
|
+
reloaded = {
|
|
43555
|
+
problem: `${reloading} is a @tool script, which runs inside the editor, so it is not reloaded from inside a call: editor_launch restart recompiles it.`
|
|
43556
|
+
};
|
|
43557
|
+
} else if (reloading !== undefined) {
|
|
43092
43558
|
try {
|
|
43093
43559
|
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { ...args, scriptPath: reloading }));
|
|
43094
43560
|
const methods = readArray(answer, "methods");
|
|
@@ -43096,29 +43562,36 @@ class GodotServer {
|
|
|
43096
43562
|
const found = readArray(answer, field);
|
|
43097
43563
|
return found ? { [field]: found.map(String) } : {};
|
|
43098
43564
|
};
|
|
43565
|
+
const asResource = (path) => path.startsWith("res://") ? path : `res://${path.replace(/^\/+/, "")}`;
|
|
43566
|
+
const script = asResource(readString(answer, "script") ?? reloading);
|
|
43567
|
+
const spelling = script === asResource(reloading) ? {} : { as: script };
|
|
43099
43568
|
const failed = answer["failed"];
|
|
43100
43569
|
if (typeof failed === "number" && failed !== 0) {
|
|
43101
43570
|
reloaded = {
|
|
43571
|
+
...spelling,
|
|
43102
43572
|
...listed("heldBefore"),
|
|
43103
43573
|
...listed("heldConstantsBefore"),
|
|
43104
43574
|
problem: failedReloadNote({
|
|
43105
|
-
script
|
|
43575
|
+
script,
|
|
43106
43576
|
code: failed,
|
|
43107
43577
|
codeName: readString(answer, "failedAs") ?? "",
|
|
43108
43578
|
heldBefore: listed("heldBefore")["heldBefore"] ?? [],
|
|
43109
43579
|
heldAfter: methods?.map(String) ?? [],
|
|
43110
43580
|
constantsBefore: listed("heldConstantsBefore")["heldConstantsBefore"] ?? [],
|
|
43111
43581
|
constantsAfter: listed("constants")["constants"] ?? [],
|
|
43112
|
-
names: projectPath === "" ? [] : this.classesAScriptNames(projectPath,
|
|
43582
|
+
names: projectPath === "" ? [] : this.classesAScriptNames(projectPath, script),
|
|
43583
|
+
restored: typeof answer["restored"] === "boolean" ? answer["restored"] : null,
|
|
43584
|
+
restoredAs: readString(answer, "restoredAs") ?? ""
|
|
43113
43585
|
})
|
|
43114
43586
|
};
|
|
43115
43587
|
} else {
|
|
43116
43588
|
reloaded = methods ? {
|
|
43589
|
+
...spelling,
|
|
43117
43590
|
methods: methods.map(String),
|
|
43118
43591
|
...listed("heldBefore"),
|
|
43119
43592
|
...listed("constants"),
|
|
43120
43593
|
...listed("heldConstantsBefore")
|
|
43121
|
-
} : { problem: `the editor would not say what ${
|
|
43594
|
+
} : { problem: `the editor would not say what ${script} has after reloading it` };
|
|
43122
43595
|
}
|
|
43123
43596
|
} catch (error) {
|
|
43124
43597
|
reloaded = { problem: `${reloading} could not be reloaded: ${errorMessage(error)}` };
|
|
@@ -43140,7 +43613,7 @@ class GodotServer {
|
|
|
43140
43613
|
notes.push("The scan finished and these classes are still not in the list the editor resolves against, so every use of them reads as an unknown identifier. Its walk skips a file another engine has already imported. Rescan again on its own, which is the measured cure and needs no change to the declaring script, or restart the editor with editor_launch restart.");
|
|
43141
43614
|
}
|
|
43142
43615
|
if (dependents !== null && dependents.notReloaded.length > 0) {
|
|
43143
|
-
notes.push(
|
|
43616
|
+
notes.push(notReloadedNote(broughtIn, dependents.notReloaded));
|
|
43144
43617
|
}
|
|
43145
43618
|
for (const group of uidsDuplicatedOnDisk) {
|
|
43146
43619
|
notes.push(`${group.join(" and ")} name one UID in their .uid files, so Godot keeps one path for it and every engine warns "UID duplicate detected". Delete the .uid of the one that is a copy and rescan: the editor writes it a UID of its own.`);
|
|
@@ -43155,6 +43628,7 @@ class GodotServer {
|
|
|
43155
43628
|
stillWorking: busy,
|
|
43156
43629
|
waitedMs: Date.now() - started,
|
|
43157
43630
|
waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
|
|
43631
|
+
reloadedAs: reloaded.as,
|
|
43158
43632
|
heldBeforeReload: reloaded.heldBefore,
|
|
43159
43633
|
reloadedMethods: reloaded.methods,
|
|
43160
43634
|
heldConstantsBeforeReload: reloaded.heldConstantsBefore,
|
|
@@ -43181,7 +43655,8 @@ class GodotServer {
|
|
|
43181
43655
|
if (tool) {
|
|
43182
43656
|
notReloaded.push({
|
|
43183
43657
|
scriptPath,
|
|
43184
|
-
problem: "a @tool script, which runs inside the editor, so it is not reloaded from here; editor_launch restart recompiles it"
|
|
43658
|
+
problem: "a @tool script, which runs inside the editor, so it is not reloaded from here; editor_launch restart recompiles it",
|
|
43659
|
+
tool: true
|
|
43185
43660
|
});
|
|
43186
43661
|
continue;
|
|
43187
43662
|
}
|
|
@@ -43189,9 +43664,10 @@ class GodotServer {
|
|
|
43189
43664
|
const answer = asParams(await this.godotBridge.invokeTool("reload_script", { scriptPath }));
|
|
43190
43665
|
const failed = answer["failed"];
|
|
43191
43666
|
if (typeof failed === "number" && failed !== 0) {
|
|
43667
|
+
const left = answer["restored"] === true ? "" : answer["restored"] === false ? "; compiling it again from the text it was built from failed too, so the editor holds it unusable until it compiles" : "; a failed compile leaves the copy the editor holds unusable until it compiles, and this editor does not put it back";
|
|
43192
43668
|
notReloaded.push({
|
|
43193
43669
|
scriptPath,
|
|
43194
|
-
problem: `it did not compile: Godot answered error ${failed}${readString(answer, "failedAs") ? `, ${readString(answer, "failedAs")}` : ""}`
|
|
43670
|
+
problem: `it did not compile: Godot answered error ${failed}${readString(answer, "failedAs") ? `, ${readString(answer, "failedAs")}` : ""}${left}`
|
|
43195
43671
|
});
|
|
43196
43672
|
} else if (readArray(answer, "methods") === undefined) {
|
|
43197
43673
|
notReloaded.push({
|
|
@@ -43263,6 +43739,35 @@ class GodotServer {
|
|
|
43263
43739
|
}
|
|
43264
43740
|
};
|
|
43265
43741
|
}
|
|
43742
|
+
async handleCreateScript(args) {
|
|
43743
|
+
const project = this.project(args);
|
|
43744
|
+
if (!project.ok) {
|
|
43745
|
+
return project.response;
|
|
43746
|
+
}
|
|
43747
|
+
const contained = this.containProjectFiles(args);
|
|
43748
|
+
if (!contained.ok) {
|
|
43749
|
+
return contained.response;
|
|
43750
|
+
}
|
|
43751
|
+
const { op: _op, projectPath: _projectPath, ...params } = contained.value;
|
|
43752
|
+
const created = await this.operation("create_script", params, project.value.path);
|
|
43753
|
+
const declared = created.ok ? readString(created.payload, "class_name") ?? "" : "";
|
|
43754
|
+
if (!created.ok || declared === "") {
|
|
43755
|
+
return this.answer(created);
|
|
43756
|
+
}
|
|
43757
|
+
const rebuilt = await this.rebuildClassCache(project.value.path);
|
|
43758
|
+
const registered = cachedClasses(project.value.path)?.get(declared) === readString(created.payload, "full_path");
|
|
43759
|
+
const answered = this.answer({
|
|
43760
|
+
...created,
|
|
43761
|
+
payload: {
|
|
43762
|
+
...created.payload,
|
|
43763
|
+
registered,
|
|
43764
|
+
...registered ? {} : {
|
|
43765
|
+
registrationNote: rebuilt.ok ? `The class list was rebuilt and does not hold ${declared} at this script, so nothing started next resolves it by name. project_import refresh_classes says what the list holds and why.` : `The class list could not be rebuilt, so nothing started next resolves ${declared} by name: ${rebuilt.message}`
|
|
43766
|
+
}
|
|
43767
|
+
}
|
|
43768
|
+
});
|
|
43769
|
+
return this.godotBridge.isConnected() ? await this.alsoSayWhatTheEditorCannotSee(answered, args) : answered;
|
|
43770
|
+
}
|
|
43266
43771
|
async alsoSayWhatTheEditorCannotSee(answered, args) {
|
|
43267
43772
|
const first = answered.content[0];
|
|
43268
43773
|
if (answered.isError || first?.type !== "text" || typeof first.text !== "string") {
|
|
@@ -43391,7 +43896,7 @@ class GodotServer {
|
|
|
43391
43896
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
43392
43897
|
const run = await this.currentRun();
|
|
43393
43898
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
43394
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
43899
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync17(join23(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
43395
43900
|
return null;
|
|
43396
43901
|
}
|
|
43397
43902
|
const already = runtimesAnnounced().running;
|
|
@@ -43415,11 +43920,15 @@ class GodotServer {
|
|
|
43415
43920
|
const seconds = Math.round((Date.now() - run.startedAt) / 1000);
|
|
43416
43921
|
return `The game this server started (${which}) has been running for ${seconds}s and has not announced its runtime, so there is nothing to talk to. A game that keeps the runtime off, as a project's own loader can for some scenes, never announces; editor_output says what it has printed.`;
|
|
43417
43922
|
}
|
|
43418
|
-
const
|
|
43923
|
+
const fresh = await announcedSince(project, new Set(already.map((one) => one.pid)), {
|
|
43419
43924
|
budgetMs: Math.min(budgetMs, remaining),
|
|
43420
43925
|
accept: (one) => this.isTheGameOf(this.activeProcess, one, project),
|
|
43421
43926
|
giveUp: () => this.activeProcess !== run || this.dapClient?.isStopped() === true || !stillRunning(run)
|
|
43422
43927
|
});
|
|
43928
|
+
const endpoint = await this.theRunsAmong(run, fresh);
|
|
43929
|
+
if (fresh.length > 0 && endpoint === null) {
|
|
43930
|
+
return null;
|
|
43931
|
+
}
|
|
43423
43932
|
if (endpoint !== null) {
|
|
43424
43933
|
const tiedMeanwhile = () => run.announcedPid !== undefined;
|
|
43425
43934
|
if (!tiedMeanwhile()) {
|
|
@@ -43471,7 +43980,7 @@ class GodotServer {
|
|
|
43471
43980
|
}
|
|
43472
43981
|
}
|
|
43473
43982
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
43474
|
-
const screenshotPath = screenshotDir ?
|
|
43983
|
+
const screenshotPath = screenshotDir ? join23(screenshotDir, "capture.png") : null;
|
|
43475
43984
|
try {
|
|
43476
43985
|
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
43477
43986
|
if (!reply.ok && reply.requestId !== undefined) {
|
|
@@ -43512,7 +44021,7 @@ class GodotServer {
|
|
|
43512
44021
|
type: "text",
|
|
43513
44022
|
text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${saveTo === null ? "" : `. Saved to ${saveTo}`}`
|
|
43514
44023
|
},
|
|
43515
|
-
{ type: "image", data:
|
|
44024
|
+
{ type: "image", data: readFileSync20(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
43516
44025
|
]
|
|
43517
44026
|
};
|
|
43518
44027
|
} finally {
|
|
@@ -43612,7 +44121,7 @@ async function runGodotServer() {
|
|
|
43612
44121
|
const server = new GodotServer;
|
|
43613
44122
|
await server.run();
|
|
43614
44123
|
}
|
|
43615
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250,
|
|
44124
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, 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/";
|
|
43616
44125
|
var init_server3 = __esm(() => {
|
|
43617
44126
|
init_mcp();
|
|
43618
44127
|
init_stdio2();
|
|
@@ -43628,6 +44137,7 @@ var init_server3 = __esm(() => {
|
|
|
43628
44137
|
init_dap_client();
|
|
43629
44138
|
init_desktop();
|
|
43630
44139
|
init_editor_log();
|
|
44140
|
+
init_engine_run();
|
|
43631
44141
|
init_errors();
|
|
43632
44142
|
init_game_log();
|
|
43633
44143
|
init_godot_bridge();
|
|
@@ -43652,8 +44162,8 @@ var init_server3 = __esm(() => {
|
|
|
43652
44162
|
init_setup();
|
|
43653
44163
|
init_tool_definitions();
|
|
43654
44164
|
init_update_check();
|
|
43655
|
-
|
|
43656
|
-
__dirname2 =
|
|
44165
|
+
run4 = promisify5(execFile5);
|
|
44166
|
+
__dirname2 = dirname16(fileURLToPath5(import.meta.url));
|
|
43657
44167
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
43658
44168
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
43659
44169
|
VALUE_NAMES = {
|
|
@@ -43702,8 +44212,8 @@ var init_server3 = __esm(() => {
|
|
|
43702
44212
|
init_bridge_announce();
|
|
43703
44213
|
init_errors();
|
|
43704
44214
|
init_godot_path();
|
|
43705
|
-
import { existsSync as
|
|
43706
|
-
import { join as
|
|
44215
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
44216
|
+
import { join as join24, resolve as resolve6 } from "node:path";
|
|
43707
44217
|
|
|
43708
44218
|
// src/harnesses.ts
|
|
43709
44219
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -44602,8 +45112,8 @@ init_setup();
|
|
|
44602
45112
|
// src/skill.ts
|
|
44603
45113
|
init_setup();
|
|
44604
45114
|
init_tool_definitions();
|
|
44605
|
-
import { existsSync as
|
|
44606
|
-
import { basename as basename3, dirname as
|
|
45115
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync5, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
45116
|
+
import { basename as basename3, dirname as dirname6, join as join11 } from "node:path";
|
|
44607
45117
|
|
|
44608
45118
|
// src/tool-reference.ts
|
|
44609
45119
|
init_headless_operations();
|
|
@@ -44736,9 +45246,10 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
44736
45246
|
- Read \`editor_output\` after every run. It returns the engine's errors and warnings as entries
|
|
44737
45247
|
with their backtraces, and a \`clean\` verdict, so a run that printed an error is one call away
|
|
44738
45248
|
from being known. For a run the editor plays, the errors come from the game through the runtime
|
|
44739
|
-
addon, so a played game without the addon
|
|
44740
|
-
|
|
44741
|
-
answer for state. Never decide a run has ended by watching the
|
|
45249
|
+
addon, so a played game without the addon answers \`errorsUnread\` and no \`clean\`, since
|
|
45250
|
+
what it raised reached nothing the server reads. For a long run it also names a transcript file:
|
|
45251
|
+
read that for output and this answer for state. Never decide a run has ended by watching the
|
|
45252
|
+
file stop growing, or a process
|
|
44742
45253
|
list, or a timeout; a bench between prints looks exactly like a bench that died to everything else.
|
|
44743
45254
|
\`endedBy\` says whether gdharness ended it, and \`running\` is asked of the operating system
|
|
44744
45255
|
whenever there is a process to ask about: every run started through \`editor_run\`, and every
|
|
@@ -44918,9 +45429,9 @@ function everySkillDirectory(projectPath, harnesses) {
|
|
|
44918
45429
|
function removeSkill(directories, projectPath) {
|
|
44919
45430
|
const removed = [];
|
|
44920
45431
|
for (const directory of directories) {
|
|
44921
|
-
if (
|
|
45432
|
+
if (existsSync11(directory)) {
|
|
44922
45433
|
rmSync6(directory, { recursive: true, force: true });
|
|
44923
|
-
pruneEmpty(
|
|
45434
|
+
pruneEmpty(dirname6(directory), projectPath);
|
|
44924
45435
|
removed.push(directory);
|
|
44925
45436
|
}
|
|
44926
45437
|
}
|
|
@@ -44934,17 +45445,17 @@ function pruneEmpty(directory, projectPath) {
|
|
|
44934
45445
|
return;
|
|
44935
45446
|
}
|
|
44936
45447
|
rmSync6(path, { recursive: true, force: true });
|
|
44937
|
-
path =
|
|
45448
|
+
path = dirname6(path);
|
|
44938
45449
|
}
|
|
44939
45450
|
}
|
|
44940
45451
|
function writeSkill(directories, version) {
|
|
44941
45452
|
const files = skillFiles(version);
|
|
44942
45453
|
return directories.map((directory) => {
|
|
44943
|
-
const replaced =
|
|
45454
|
+
const replaced = existsSync11(directory);
|
|
44944
45455
|
rmSync6(directory, { recursive: true, force: true });
|
|
44945
45456
|
for (const [name, contents] of files) {
|
|
44946
45457
|
const path = join11(directory, name);
|
|
44947
|
-
mkdirSync5(
|
|
45458
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
44948
45459
|
writeFileSync7(path, contents, "utf8");
|
|
44949
45460
|
}
|
|
44950
45461
|
return { path: directory, replaced };
|
|
@@ -44952,18 +45463,18 @@ function writeSkill(directories, version) {
|
|
|
44952
45463
|
}
|
|
44953
45464
|
|
|
44954
45465
|
// src/version.ts
|
|
44955
|
-
import { readFileSync as
|
|
44956
|
-
import { dirname as
|
|
45466
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
45467
|
+
import { dirname as dirname7, join as join12 } from "node:path";
|
|
44957
45468
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
44958
45469
|
function getLocalVersion() {
|
|
44959
|
-
const currentDirectory =
|
|
45470
|
+
const currentDirectory = dirname7(fileURLToPath2(import.meta.url));
|
|
44960
45471
|
const packageCandidates = [
|
|
44961
45472
|
join12(currentDirectory, "..", "package.json"),
|
|
44962
45473
|
join12(currentDirectory, "..", "..", "package.json")
|
|
44963
45474
|
];
|
|
44964
45475
|
for (const packagePath of packageCandidates) {
|
|
44965
45476
|
try {
|
|
44966
|
-
const value = JSON.parse(
|
|
45477
|
+
const value = JSON.parse(readFileSync10(packagePath, "utf-8"));
|
|
44967
45478
|
if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
|
|
44968
45479
|
return value.version;
|
|
44969
45480
|
}
|
|
@@ -44981,7 +45492,7 @@ class UsageError extends Refusal {
|
|
|
44981
45492
|
function projectArgument(at) {
|
|
44982
45493
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
44983
45494
|
const projectPath = resolve6(named ?? ".");
|
|
44984
|
-
if (!
|
|
45495
|
+
if (!existsSync18(join24(projectPath, "project.godot"))) {
|
|
44985
45496
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
44986
45497
|
}
|
|
44987
45498
|
return projectPath;
|
|
@@ -45020,7 +45531,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
45020
45531
|
return;
|
|
45021
45532
|
}
|
|
45022
45533
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
45023
|
-
if (!
|
|
45534
|
+
if (!existsSync18(join24(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
45024
45535
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
45025
45536
|
}
|
|
45026
45537
|
}
|
|
@@ -45152,7 +45663,7 @@ async function setup() {
|
|
|
45152
45663
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
45153
45664
|
}
|
|
45154
45665
|
if (!args.includes("--no-skill")) {
|
|
45155
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
45666
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync18);
|
|
45156
45667
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
45157
45668
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
45158
45669
|
}
|
|
@@ -45246,7 +45757,7 @@ async function upgrade() {
|
|
|
45246
45757
|
moved.push(took.from);
|
|
45247
45758
|
}
|
|
45248
45759
|
}
|
|
45249
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
45760
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync18), version)) {
|
|
45250
45761
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
45251
45762
|
}
|
|
45252
45763
|
console.log(`
|