gdharness 1.2.36 → 1.2.38
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 +918 -439
- package/build/godot/addons/gdharness_editor/headless_check.gd +411 -0
- package/build/godot/addons/gdharness_editor/headless_check.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +3 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +408 -10
- package/build/godot/addons/gdharness_editor/serialisation.gd +103 -5
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +88 -25
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +68 -19
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +9 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +3 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +103 -5
- package/build/godot/operations/editor_tools.gd +57 -0
- package/build/godot/operations/gdscript_analysis.gd +26 -2
- package/build/godot/operations/gdscript_authoring.gd +5 -2
- package/build/godot/operations/godot_operations.gd +58 -1
- package/build/godot/operations/property_values.gd +3 -0
- package/build/godot/operations/serialisation.gd +103 -5
- package/build/index.js +838 -366
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -3365,17 +3365,18 @@ import {
|
|
|
3365
3365
|
closeSync as closeSync3,
|
|
3366
3366
|
constants,
|
|
3367
3367
|
copyFileSync,
|
|
3368
|
-
existsSync as
|
|
3368
|
+
existsSync as existsSync21,
|
|
3369
3369
|
fstatSync,
|
|
3370
3370
|
mkdirSync as mkdirSync15,
|
|
3371
3371
|
openSync as openSync4,
|
|
3372
|
-
readdirSync as
|
|
3372
|
+
readdirSync as readdirSync12,
|
|
3373
3373
|
readFileSync as readFileSync24,
|
|
3374
3374
|
readSync as readSync2,
|
|
3375
3375
|
rmdirSync as rmdirSync2,
|
|
3376
|
-
statSync as
|
|
3376
|
+
statSync as statSync13,
|
|
3377
|
+
writeFileSync as writeFileSync16
|
|
3377
3378
|
} from "node:fs";
|
|
3378
|
-
import { basename as
|
|
3379
|
+
import { basename as basename8, dirname as dirname19, extname as extname4, isAbsolute as isAbsolute5, join as join27, normalize as normalize2, relative as relative5 } from "node:path";
|
|
3379
3380
|
import { setTimeout as delay5 } from "node:timers/promises";
|
|
3380
3381
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3381
3382
|
|
|
@@ -28537,19 +28538,11 @@ function dictionary(entries) {
|
|
|
28537
28538
|
return Object.assign(emptyRecord(), entries);
|
|
28538
28539
|
}
|
|
28539
28540
|
|
|
28540
|
-
// src/
|
|
28541
|
-
import {
|
|
28542
|
-
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
openSync as openSync2,
|
|
28546
|
-
readFileSync as readFileSync12,
|
|
28547
|
-
readSync,
|
|
28548
|
-
rmSync as rmSync6,
|
|
28549
|
-
statSync as statSync5,
|
|
28550
|
-
writeFileSync as writeFileSync8
|
|
28551
|
-
} from "node:fs";
|
|
28552
|
-
import { dirname as dirname7, join as join11 } from "node:path";
|
|
28541
|
+
// src/doc-cache.ts
|
|
28542
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync5 } from "node:fs";
|
|
28543
|
+
import { homedir as homedir2 } from "node:os";
|
|
28544
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute3, join as join11 } from "node:path";
|
|
28545
|
+
import process7 from "node:process";
|
|
28553
28546
|
|
|
28554
28547
|
// src/game-log.ts
|
|
28555
28548
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -28757,6 +28750,9 @@ class GameLog {
|
|
|
28757
28750
|
count(severity) {
|
|
28758
28751
|
return this.entries.filter((entry) => entry.severity === severity).length;
|
|
28759
28752
|
}
|
|
28753
|
+
crashReport() {
|
|
28754
|
+
return this.entries.find((entry) => CRASH.test(entry.text));
|
|
28755
|
+
}
|
|
28760
28756
|
everything() {
|
|
28761
28757
|
return this.entries;
|
|
28762
28758
|
}
|
|
@@ -28790,12 +28786,131 @@ function answersTo(entry, severity, contains) {
|
|
|
28790
28786
|
return [entry.text, ...entry.detail].some((line) => line.toLowerCase().includes(needle));
|
|
28791
28787
|
}
|
|
28792
28788
|
|
|
28789
|
+
// src/engine-run.ts
|
|
28790
|
+
function runEngine(executable, args, options = {}) {
|
|
28791
|
+
return new Promise((resolve) => {
|
|
28792
|
+
const log = new GameLog;
|
|
28793
|
+
let failure = null;
|
|
28794
|
+
const signal = callSignal();
|
|
28795
|
+
const child = spawnHidden(executable, args, {
|
|
28796
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
28797
|
+
...signal === undefined ? {} : { signal }
|
|
28798
|
+
});
|
|
28799
|
+
child.stdout.on("data", (chunk) => {
|
|
28800
|
+
log.append("stdout", chunk);
|
|
28801
|
+
});
|
|
28802
|
+
child.stderr.on("data", (chunk) => {
|
|
28803
|
+
log.append("stderr", chunk);
|
|
28804
|
+
});
|
|
28805
|
+
const { timeout } = options;
|
|
28806
|
+
const timer = timeout === undefined ? undefined : setTimeout(() => {
|
|
28807
|
+
failure ??= `it ran past its ${timeout.said}`;
|
|
28808
|
+
child.kill();
|
|
28809
|
+
}, timeout.ms);
|
|
28810
|
+
child.on("error", (error) => {
|
|
28811
|
+
failure ??= error.name === "AbortError" ? "the call was cancelled" : `it could not be started: ${error.message}`;
|
|
28812
|
+
});
|
|
28813
|
+
child.on("close", (code, closedBy) => {
|
|
28814
|
+
clearTimeout(timer);
|
|
28815
|
+
log.finish();
|
|
28816
|
+
const started = child.pid !== undefined;
|
|
28817
|
+
resolve({ log, exitCode: started ? code : null, exitSignal: closedBy, failure });
|
|
28818
|
+
});
|
|
28819
|
+
});
|
|
28820
|
+
}
|
|
28821
|
+
var WINDOWS_STATUSES = new Map([
|
|
28822
|
+
[3221225477, "an access violation, so it crashed"],
|
|
28823
|
+
[3221225501, "an illegal instruction, so it crashed"],
|
|
28824
|
+
[3221225620, "an integer division by zero, so it crashed"],
|
|
28825
|
+
[3221225725, "a stack overflow, so it crashed"],
|
|
28826
|
+
[3221225781, "a library it needs was not found, so it never started"],
|
|
28827
|
+
[3221225786, "its console was closed or sent Ctrl+C"],
|
|
28828
|
+
[3221226356, "a corrupted heap, so it crashed"],
|
|
28829
|
+
[3221226505, "a stack buffer overrun or a fast fail, so it crashed"]
|
|
28830
|
+
]);
|
|
28831
|
+
function exitCodeText(code) {
|
|
28832
|
+
if (code === null) {
|
|
28833
|
+
return "exit code unknown";
|
|
28834
|
+
}
|
|
28835
|
+
if (code < 3221225472) {
|
|
28836
|
+
return `exit code ${code}`;
|
|
28837
|
+
}
|
|
28838
|
+
const hex = `0x${code.toString(16).toUpperCase()}`;
|
|
28839
|
+
return `exit code ${hex}, ${WINDOWS_STATUSES.get(code) ?? "a Windows error status"}`;
|
|
28840
|
+
}
|
|
28841
|
+
function howItEnded(run) {
|
|
28842
|
+
if (run.failure !== null) {
|
|
28843
|
+
return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
|
|
28844
|
+
}
|
|
28845
|
+
return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}`;
|
|
28846
|
+
}
|
|
28847
|
+
|
|
28848
|
+
// src/doc-cache.ts
|
|
28849
|
+
var CACHE_FILE = /^editor_doc_cache-\d+\.\d+\.res$/;
|
|
28850
|
+
function editorCacheDirectory(godotPath, platform = process7.platform, env = process7.env, home = homedir2()) {
|
|
28851
|
+
const beside = dirname7(godotPath);
|
|
28852
|
+
const bundle = platform === "darwin" && basename2(beside) === "MacOS" && basename2(dirname7(beside)) === "Contents" ? dirname7(dirname7(dirname7(beside))) : null;
|
|
28853
|
+
for (const folder of bundle === null ? [beside] : [beside, bundle]) {
|
|
28854
|
+
if (existsSync7(join11(folder, "._sc_")) || existsSync7(join11(folder, "_sc_"))) {
|
|
28855
|
+
return join11(folder, "editor_data", "cache");
|
|
28856
|
+
}
|
|
28857
|
+
}
|
|
28858
|
+
if (platform === "win32") {
|
|
28859
|
+
const set = (name) => env[name] === "" ? undefined : env[name];
|
|
28860
|
+
return join11(set("LOCALAPPDATA") ?? set("TEMP") ?? join11(home, "AppData", "Local"), "Godot");
|
|
28861
|
+
}
|
|
28862
|
+
if (platform === "darwin") {
|
|
28863
|
+
return join11(home, "Library", "Caches", "Godot");
|
|
28864
|
+
}
|
|
28865
|
+
const xdg = env["XDG_CACHE_HOME"];
|
|
28866
|
+
return join11(xdg !== undefined && isAbsolute3(xdg) ? xdg : join11(home, ".cache"), "godot");
|
|
28867
|
+
}
|
|
28868
|
+
function docCachesWritten(directory) {
|
|
28869
|
+
const written = new Map;
|
|
28870
|
+
let names;
|
|
28871
|
+
try {
|
|
28872
|
+
names = readdirSync3(directory);
|
|
28873
|
+
} catch {
|
|
28874
|
+
return written;
|
|
28875
|
+
}
|
|
28876
|
+
for (const name of names.filter((one) => CACHE_FILE.test(one))) {
|
|
28877
|
+
try {
|
|
28878
|
+
written.set(join11(directory, name), statSync5(join11(directory, name)).mtimeMs);
|
|
28879
|
+
} catch {}
|
|
28880
|
+
}
|
|
28881
|
+
return written;
|
|
28882
|
+
}
|
|
28883
|
+
function docCacheCrash(ran, before, after, what) {
|
|
28884
|
+
const report = ran.log.crashReport();
|
|
28885
|
+
if (ran.exitCode === 0 && ran.exitSignal === null && report === undefined || ran.failure !== null) {
|
|
28886
|
+
return null;
|
|
28887
|
+
}
|
|
28888
|
+
const rewritten = [...after].find(([path, at]) => before.get(path) !== at)?.[0];
|
|
28889
|
+
if (rewritten === undefined) {
|
|
28890
|
+
return null;
|
|
28891
|
+
}
|
|
28892
|
+
const said = report === undefined ? "" : `, and its crash handler printed "${report.text}"`;
|
|
28893
|
+
return `The first ${what} ended badly (${howItEnded(ran)}${said}) after rebuilding the editor's class reference ` + `cache, ${rewritten}. Godot crashes as an import or an export exits when it finds that cache ` + "stale, which it is whenever another build of the same minor version wrote it last, such as 4.7.1 " + `beside 4.7.2, and the crashing run writes a fresh one. So the ${what} was run again, and this answer ` + "is from that second run.";
|
|
28894
|
+
}
|
|
28895
|
+
|
|
28793
28896
|
// src/editor-log.ts
|
|
28897
|
+
import {
|
|
28898
|
+
closeSync as closeSync2,
|
|
28899
|
+
existsSync as existsSync8,
|
|
28900
|
+
mkdirSync as mkdirSync7,
|
|
28901
|
+
openSync as openSync2,
|
|
28902
|
+
readFileSync as readFileSync12,
|
|
28903
|
+
readSync,
|
|
28904
|
+
rmSync as rmSync6,
|
|
28905
|
+
statSync as statSync6,
|
|
28906
|
+
writeFileSync as writeFileSync8
|
|
28907
|
+
} from "node:fs";
|
|
28908
|
+
import { dirname as dirname8, join as join12 } from "node:path";
|
|
28794
28909
|
function editorLogPath(projectPath) {
|
|
28795
|
-
return
|
|
28910
|
+
return join12(projectPath, ".godot", "gdharness-editor.log");
|
|
28796
28911
|
}
|
|
28797
28912
|
function editorLogNotePath(projectPath) {
|
|
28798
|
-
return
|
|
28913
|
+
return join12(projectPath, ".godot", "gdharness-editor.json");
|
|
28799
28914
|
}
|
|
28800
28915
|
function readEditorLogNote(projectPath) {
|
|
28801
28916
|
try {
|
|
@@ -28810,7 +28925,7 @@ function readEditorLogNote(projectPath) {
|
|
|
28810
28925
|
function clearEditorLog(projectPath) {
|
|
28811
28926
|
try {
|
|
28812
28927
|
const path = editorLogPath(projectPath);
|
|
28813
|
-
mkdirSync7(
|
|
28928
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
28814
28929
|
rmSync6(path, { force: true });
|
|
28815
28930
|
rmSync6(editorLogNotePath(projectPath), { force: true });
|
|
28816
28931
|
} catch {}
|
|
@@ -28818,7 +28933,7 @@ function clearEditorLog(projectPath) {
|
|
|
28818
28933
|
function writeEditorLogNote(projectPath, pid) {
|
|
28819
28934
|
try {
|
|
28820
28935
|
const path = editorLogNotePath(projectPath);
|
|
28821
|
-
mkdirSync7(
|
|
28936
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
28822
28937
|
writeFileSync8(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
28823
28938
|
} catch {}
|
|
28824
28939
|
}
|
|
@@ -28840,7 +28955,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
28840
28955
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
28841
28956
|
}
|
|
28842
28957
|
const path = editorLogPath(projectPath);
|
|
28843
|
-
if (!
|
|
28958
|
+
if (!existsSync8(path)) {
|
|
28844
28959
|
return { kind: "not written" };
|
|
28845
28960
|
}
|
|
28846
28961
|
const log = new GameLog;
|
|
@@ -28899,7 +29014,7 @@ function foldProgress(text) {
|
|
|
28899
29014
|
}
|
|
28900
29015
|
var MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
|
|
28901
29016
|
function theFirstOf(path) {
|
|
28902
|
-
const whole =
|
|
29017
|
+
const whole = statSync6(path).size;
|
|
28903
29018
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
28904
29019
|
return { text: readFileSync12(path, "utf8") };
|
|
28905
29020
|
}
|
|
@@ -29056,60 +29171,21 @@ function linesBefore(entries, at, collapsed, wanted) {
|
|
|
29056
29171
|
return found.reverse();
|
|
29057
29172
|
}
|
|
29058
29173
|
|
|
29059
|
-
// src/engine-run.ts
|
|
29060
|
-
function runEngine(executable, args, options = {}) {
|
|
29061
|
-
return new Promise((resolve) => {
|
|
29062
|
-
const log = new GameLog;
|
|
29063
|
-
let failure = null;
|
|
29064
|
-
const signal = callSignal();
|
|
29065
|
-
const child = spawnHidden(executable, args, {
|
|
29066
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
29067
|
-
...signal === undefined ? {} : { signal }
|
|
29068
|
-
});
|
|
29069
|
-
child.stdout.on("data", (chunk) => {
|
|
29070
|
-
log.append("stdout", chunk);
|
|
29071
|
-
});
|
|
29072
|
-
child.stderr.on("data", (chunk) => {
|
|
29073
|
-
log.append("stderr", chunk);
|
|
29074
|
-
});
|
|
29075
|
-
const { timeout } = options;
|
|
29076
|
-
const timer = timeout === undefined ? undefined : setTimeout(() => {
|
|
29077
|
-
failure ??= `it ran past its ${timeout.said}`;
|
|
29078
|
-
child.kill();
|
|
29079
|
-
}, timeout.ms);
|
|
29080
|
-
child.on("error", (error) => {
|
|
29081
|
-
failure ??= error.name === "AbortError" ? "the call was cancelled" : `it could not be started: ${error.message}`;
|
|
29082
|
-
});
|
|
29083
|
-
child.on("close", (code, closedBy) => {
|
|
29084
|
-
clearTimeout(timer);
|
|
29085
|
-
log.finish();
|
|
29086
|
-
const started = child.pid !== undefined;
|
|
29087
|
-
resolve({ log, exitCode: started ? code : null, exitSignal: closedBy, failure });
|
|
29088
|
-
});
|
|
29089
|
-
});
|
|
29090
|
-
}
|
|
29091
|
-
function howItEnded(run) {
|
|
29092
|
-
if (run.failure !== null) {
|
|
29093
|
-
return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
|
|
29094
|
-
}
|
|
29095
|
-
return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
|
|
29096
|
-
}
|
|
29097
|
-
|
|
29098
29174
|
// src/file-move.ts
|
|
29099
29175
|
import {
|
|
29100
|
-
existsSync as
|
|
29176
|
+
existsSync as existsSync13,
|
|
29101
29177
|
mkdirSync as mkdirSync10,
|
|
29102
|
-
readdirSync as
|
|
29178
|
+
readdirSync as readdirSync6,
|
|
29103
29179
|
readFileSync as readFileSync16,
|
|
29104
29180
|
renameSync as renameSync3,
|
|
29105
|
-
statSync as
|
|
29181
|
+
statSync as statSync7,
|
|
29106
29182
|
writeFileSync as writeFileSync11
|
|
29107
29183
|
} from "node:fs";
|
|
29108
|
-
import { basename as
|
|
29184
|
+
import { basename as basename4, dirname as dirname12, extname as extname3, join as join16, relative as relative4 } from "node:path";
|
|
29109
29185
|
|
|
29110
29186
|
// src/project-scan.ts
|
|
29111
|
-
import { existsSync as
|
|
29112
|
-
import { extname, join as
|
|
29187
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync13 } from "node:fs";
|
|
29188
|
+
import { extname, join as join13 } from "node:path";
|
|
29113
29189
|
var SKIPPED = new Set(["node_modules"]);
|
|
29114
29190
|
function skipped(name) {
|
|
29115
29191
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
@@ -29119,10 +29195,10 @@ function steppedOver(directory) {
|
|
|
29119
29195
|
return gdignored(directory) || installedByUs(directory);
|
|
29120
29196
|
}
|
|
29121
29197
|
function gdignored(directory) {
|
|
29122
|
-
return
|
|
29198
|
+
return existsSync9(join13(directory, ".gdignore"));
|
|
29123
29199
|
}
|
|
29124
29200
|
function installedByUs(directory) {
|
|
29125
|
-
return
|
|
29201
|
+
return existsSync9(join13(directory, VERSION_MARKER));
|
|
29126
29202
|
}
|
|
29127
29203
|
var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
29128
29204
|
var ASSET_EXTENSIONS = new Set([
|
|
@@ -29161,12 +29237,12 @@ function projectStructure(projectPath) {
|
|
|
29161
29237
|
if (steppedOver(directory)) {
|
|
29162
29238
|
return;
|
|
29163
29239
|
}
|
|
29164
|
-
for (const entry of
|
|
29240
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29165
29241
|
if (skipped(entry.name)) {
|
|
29166
29242
|
continue;
|
|
29167
29243
|
}
|
|
29168
29244
|
if (entry.isDirectory()) {
|
|
29169
|
-
visit(
|
|
29245
|
+
visit(join13(directory, entry.name));
|
|
29170
29246
|
} else if (entry.isFile()) {
|
|
29171
29247
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
29172
29248
|
if (SCENE_EXTENSIONS.has(extension)) {
|
|
@@ -29190,13 +29266,13 @@ function projectFiles(projectPath, extensions) {
|
|
|
29190
29266
|
if (steppedOver(directory)) {
|
|
29191
29267
|
return;
|
|
29192
29268
|
}
|
|
29193
|
-
for (const entry of
|
|
29269
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29194
29270
|
if (skipped(entry.name)) {
|
|
29195
29271
|
continue;
|
|
29196
29272
|
}
|
|
29197
29273
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
29198
29274
|
if (entry.isDirectory()) {
|
|
29199
|
-
visit(
|
|
29275
|
+
visit(join13(directory, entry.name), spelled);
|
|
29200
29276
|
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
29201
29277
|
found.push(spelled);
|
|
29202
29278
|
}
|
|
@@ -29212,20 +29288,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
29212
29288
|
if (steppedOver(directory)) {
|
|
29213
29289
|
return;
|
|
29214
29290
|
}
|
|
29215
|
-
for (const entry of
|
|
29291
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29216
29292
|
if (skipped(entry.name)) {
|
|
29217
29293
|
continue;
|
|
29218
29294
|
}
|
|
29219
29295
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
29220
29296
|
if (entry.isDirectory()) {
|
|
29221
|
-
visit(
|
|
29297
|
+
visit(join13(directory, entry.name), spelled);
|
|
29222
29298
|
continue;
|
|
29223
29299
|
}
|
|
29224
29300
|
if (!entry.isFile()) {
|
|
29225
29301
|
continue;
|
|
29226
29302
|
}
|
|
29227
29303
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
29228
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
29304
|
+
if (CARRIES_A_UID.has(extension) && !existsSync9(join13(directory, `${entry.name}.uid`))) {
|
|
29229
29305
|
found.push(spelled);
|
|
29230
29306
|
}
|
|
29231
29307
|
}
|
|
@@ -29253,11 +29329,11 @@ function projectTexts(projectPath) {
|
|
|
29253
29329
|
if (steppedOver(directory)) {
|
|
29254
29330
|
return;
|
|
29255
29331
|
}
|
|
29256
|
-
for (const entry of
|
|
29332
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29257
29333
|
if (skipped(entry.name)) {
|
|
29258
29334
|
continue;
|
|
29259
29335
|
}
|
|
29260
|
-
const absolute =
|
|
29336
|
+
const absolute = join13(directory, entry.name);
|
|
29261
29337
|
const spelled = `${prefix}${entry.name}`;
|
|
29262
29338
|
if (entry.isDirectory()) {
|
|
29263
29339
|
visit(absolute, `${spelled}/`);
|
|
@@ -29283,11 +29359,11 @@ function ignoredTexts(projectPath) {
|
|
|
29283
29359
|
return;
|
|
29284
29360
|
}
|
|
29285
29361
|
const under = ignored || gdignored(directory);
|
|
29286
|
-
for (const entry of
|
|
29362
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29287
29363
|
if (skipped(entry.name)) {
|
|
29288
29364
|
continue;
|
|
29289
29365
|
}
|
|
29290
|
-
const absolute =
|
|
29366
|
+
const absolute = join13(directory, entry.name);
|
|
29291
29367
|
const spelled = `${prefix}${entry.name}`;
|
|
29292
29368
|
if (entry.isDirectory()) {
|
|
29293
29369
|
visit(absolute, `${spelled}/`, under);
|
|
@@ -29320,14 +29396,14 @@ function searchProject(projectPath, options) {
|
|
|
29320
29396
|
if (steppedOver(directory)) {
|
|
29321
29397
|
return;
|
|
29322
29398
|
}
|
|
29323
|
-
for (const entry of
|
|
29399
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29324
29400
|
if (full()) {
|
|
29325
29401
|
return;
|
|
29326
29402
|
}
|
|
29327
29403
|
if (skipped(entry.name)) {
|
|
29328
29404
|
continue;
|
|
29329
29405
|
}
|
|
29330
|
-
const entryPath =
|
|
29406
|
+
const entryPath = join13(directory, entry.name);
|
|
29331
29407
|
if (entry.isDirectory()) {
|
|
29332
29408
|
visit(entryPath);
|
|
29333
29409
|
continue;
|
|
@@ -29366,8 +29442,8 @@ function searchProject(projectPath, options) {
|
|
|
29366
29442
|
}
|
|
29367
29443
|
|
|
29368
29444
|
// src/rename.ts
|
|
29369
|
-
import { existsSync as
|
|
29370
|
-
import { dirname as
|
|
29445
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync14, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
29446
|
+
import { dirname as dirname9, posix as posix2 } from "node:path";
|
|
29371
29447
|
class Script {
|
|
29372
29448
|
path;
|
|
29373
29449
|
text;
|
|
@@ -30096,21 +30172,21 @@ function applyRewrites(rewrites) {
|
|
|
30096
30172
|
if (now !== rewrite.before) {
|
|
30097
30173
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
30098
30174
|
}
|
|
30099
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
30175
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync10(rewrite.writeTo)) {
|
|
30100
30176
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
30101
30177
|
}
|
|
30102
30178
|
}
|
|
30103
30179
|
const done = [];
|
|
30104
30180
|
try {
|
|
30105
30181
|
for (const rewrite of rewrites) {
|
|
30106
|
-
mkdirSync8(
|
|
30182
|
+
mkdirSync8(dirname9(rewrite.writeTo), { recursive: true });
|
|
30107
30183
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
30108
30184
|
writeFileSync9(staging, rewrite.after, "utf8");
|
|
30109
30185
|
renameSync2(staging, rewrite.writeTo);
|
|
30110
30186
|
let movedUid = false;
|
|
30111
30187
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
30112
30188
|
unlinkSync2(rewrite.absolute);
|
|
30113
|
-
if (
|
|
30189
|
+
if (existsSync10(`${rewrite.absolute}.uid`)) {
|
|
30114
30190
|
renameSync2(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
30115
30191
|
movedUid = true;
|
|
30116
30192
|
}
|
|
@@ -30134,14 +30210,14 @@ function applyRewrites(rewrites) {
|
|
|
30134
30210
|
}
|
|
30135
30211
|
|
|
30136
30212
|
// src/rename-symbol.ts
|
|
30137
|
-
import { existsSync as
|
|
30138
|
-
import { basename as
|
|
30213
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync5, realpathSync as realpathSync4, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
30214
|
+
import { basename as basename3, dirname as dirname11, join as join15, relative as relative3, resolve as resolve5 } from "node:path";
|
|
30139
30215
|
|
|
30140
30216
|
// src/lsp_client.ts
|
|
30141
|
-
import { existsSync as
|
|
30217
|
+
import { existsSync as existsSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
30142
30218
|
import { readFile, realpath } from "node:fs/promises";
|
|
30143
30219
|
import { createConnection as createConnection3 } from "node:net";
|
|
30144
|
-
import { dirname as
|
|
30220
|
+
import { dirname as dirname10, join as join14, resolve as resolve3 } from "node:path";
|
|
30145
30221
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
30146
30222
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
30147
30223
|
var DEFAULT_LSP_PORT = 6005;
|
|
@@ -30339,7 +30415,7 @@ class GodotLSPClient {
|
|
|
30339
30415
|
if (this.initialized) {
|
|
30340
30416
|
return;
|
|
30341
30417
|
}
|
|
30342
|
-
const rootPath = this.rootPath ??
|
|
30418
|
+
const rootPath = this.rootPath ?? dirname10(resolve3(filePath));
|
|
30343
30419
|
await this.initialize(rootPath);
|
|
30344
30420
|
}
|
|
30345
30421
|
async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
|
|
@@ -30912,7 +30988,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
30912
30988
|
} catch {
|
|
30913
30989
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
30914
30990
|
}
|
|
30915
|
-
if (!
|
|
30991
|
+
if (!existsSync11(join14(projectPath, "project.godot"))) {
|
|
30916
30992
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
30917
30993
|
}
|
|
30918
30994
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -31287,13 +31363,13 @@ function onDiskSpelling(path) {
|
|
|
31287
31363
|
let at = resolve5(path);
|
|
31288
31364
|
for (;; ) {
|
|
31289
31365
|
try {
|
|
31290
|
-
return
|
|
31366
|
+
return join15(realpathSync4.native(at), ...missing.reverse());
|
|
31291
31367
|
} catch {
|
|
31292
|
-
const parent =
|
|
31368
|
+
const parent = dirname11(at);
|
|
31293
31369
|
if (parent === at) {
|
|
31294
31370
|
return resolve5(path);
|
|
31295
31371
|
}
|
|
31296
|
-
missing.push(
|
|
31372
|
+
missing.push(basename3(at));
|
|
31297
31373
|
at = parent;
|
|
31298
31374
|
}
|
|
31299
31375
|
}
|
|
@@ -31340,7 +31416,7 @@ async function renameSymbol(request, services) {
|
|
|
31340
31416
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
31341
31417
|
};
|
|
31342
31418
|
}
|
|
31343
|
-
if (
|
|
31419
|
+
if (existsSync12(request.newScriptPath)) {
|
|
31344
31420
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
31345
31421
|
}
|
|
31346
31422
|
}
|
|
@@ -31516,7 +31592,7 @@ async function renameSymbol(request, services) {
|
|
|
31516
31592
|
scripts,
|
|
31517
31593
|
moves: new Map([[declaringPath, moveTo]]),
|
|
31518
31594
|
named: [declaringPath],
|
|
31519
|
-
exists: (path) =>
|
|
31595
|
+
exists: (path) => existsSync12(`${projectPath}/${path.slice("res://".length)}`)
|
|
31520
31596
|
}, plan);
|
|
31521
31597
|
}
|
|
31522
31598
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
|
|
@@ -31717,15 +31793,15 @@ ${mention.kind}`;
|
|
|
31717
31793
|
var LEFT_ALONE_ANSWER_LINES = 300;
|
|
31718
31794
|
var detailWrites = 0;
|
|
31719
31795
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
31720
|
-
const directory =
|
|
31796
|
+
const directory = join15(projectPath, ".godot", "gdharness-renames");
|
|
31721
31797
|
mkdirSync9(directory, { recursive: true });
|
|
31722
31798
|
detailWrites += 1;
|
|
31723
|
-
const path =
|
|
31799
|
+
const path = join15(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
31724
31800
|
writeFileSync10(path, `${JSON.stringify(detail, null, 2)}
|
|
31725
31801
|
`, "utf8");
|
|
31726
|
-
const kept =
|
|
31802
|
+
const kept = readdirSync5(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
31727
31803
|
for (const old of kept.slice(10)) {
|
|
31728
|
-
rmSync7(
|
|
31804
|
+
rmSync7(join15(directory, old), { force: true });
|
|
31729
31805
|
}
|
|
31730
31806
|
return path;
|
|
31731
31807
|
}
|
|
@@ -31750,15 +31826,15 @@ function summaryOf(edits, mentions, rewrites, left) {
|
|
|
31750
31826
|
// src/file-move.ts
|
|
31751
31827
|
var SIDECARS = [".uid", ".import"];
|
|
31752
31828
|
function filesUnder(directory) {
|
|
31753
|
-
return
|
|
31754
|
-
const path =
|
|
31829
|
+
return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
31830
|
+
const path = join16(directory, entry.name);
|
|
31755
31831
|
return entry.isDirectory() ? filesUnder(path) : [path];
|
|
31756
31832
|
});
|
|
31757
31833
|
}
|
|
31758
31834
|
function sameFile(one, other) {
|
|
31759
31835
|
try {
|
|
31760
|
-
const a =
|
|
31761
|
-
const b =
|
|
31836
|
+
const a = statSync7(one);
|
|
31837
|
+
const b = statSync7(other);
|
|
31762
31838
|
return a.ino === b.ino && a.dev === b.dev;
|
|
31763
31839
|
} catch {
|
|
31764
31840
|
return false;
|
|
@@ -31772,8 +31848,8 @@ function unseenByTheEngine(projectPath, path) {
|
|
|
31772
31848
|
return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
|
|
31773
31849
|
}
|
|
31774
31850
|
if (at < steps.length - 1) {
|
|
31775
|
-
directory =
|
|
31776
|
-
if (
|
|
31851
|
+
directory = join16(directory, step);
|
|
31852
|
+
if (existsSync13(join16(directory, ".gdignore"))) {
|
|
31777
31853
|
return `${path} is under a directory holding a .gdignore, which the engine does not load`;
|
|
31778
31854
|
}
|
|
31779
31855
|
}
|
|
@@ -31785,21 +31861,21 @@ function installedAddonOf(projectPath, path) {
|
|
|
31785
31861
|
if (steps[0] !== "addons" || steps[1] === undefined) {
|
|
31786
31862
|
return null;
|
|
31787
31863
|
}
|
|
31788
|
-
return
|
|
31864
|
+
return existsSync13(join16(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
|
|
31789
31865
|
}
|
|
31790
31866
|
function binaryResources(projectPath) {
|
|
31791
31867
|
const found = [];
|
|
31792
31868
|
const visit = (directory, prefix) => {
|
|
31793
|
-
if (
|
|
31869
|
+
if (existsSync13(join16(directory, ".gdignore"))) {
|
|
31794
31870
|
return;
|
|
31795
31871
|
}
|
|
31796
|
-
for (const entry of
|
|
31872
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31797
31873
|
if (entry.name.startsWith(".")) {
|
|
31798
31874
|
continue;
|
|
31799
31875
|
}
|
|
31800
31876
|
const path = `${prefix}${entry.name}`;
|
|
31801
31877
|
if (entry.isDirectory()) {
|
|
31802
|
-
visit(
|
|
31878
|
+
visit(join16(directory, entry.name), `${path}/`);
|
|
31803
31879
|
} else if (/\.(scn|res)$/i.test(entry.name)) {
|
|
31804
31880
|
found.push(path);
|
|
31805
31881
|
}
|
|
@@ -31809,7 +31885,7 @@ function binaryResources(projectPath) {
|
|
|
31809
31885
|
return found;
|
|
31810
31886
|
}
|
|
31811
31887
|
function targetPathOf(projectPath, absolute) {
|
|
31812
|
-
const inside = relative4(onDiskSpelling(projectPath),
|
|
31888
|
+
const inside = relative4(onDiskSpelling(projectPath), join16(onDiskSpelling(dirname12(absolute)), basename4(absolute)));
|
|
31813
31889
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
31814
31890
|
return null;
|
|
31815
31891
|
}
|
|
@@ -31827,13 +31903,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31827
31903
|
if (to === null) {
|
|
31828
31904
|
return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
|
|
31829
31905
|
}
|
|
31830
|
-
if (!
|
|
31906
|
+
if (!existsSync13(absoluteFrom)) {
|
|
31831
31907
|
return { ok: false, reason: `There is nothing at ${from} to move.` };
|
|
31832
31908
|
}
|
|
31833
31909
|
if (from === to) {
|
|
31834
31910
|
return { ok: false, reason: `${from} is already there; nothing to move.` };
|
|
31835
31911
|
}
|
|
31836
|
-
const isDirectory =
|
|
31912
|
+
const isDirectory = statSync7(absoluteFrom).isDirectory();
|
|
31837
31913
|
if (from === "res://project.godot") {
|
|
31838
31914
|
return {
|
|
31839
31915
|
ok: false,
|
|
@@ -31856,7 +31932,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31856
31932
|
if (isDirectory && to.startsWith(`${from}/`)) {
|
|
31857
31933
|
return { ok: false, reason: `${from} cannot be moved into itself.` };
|
|
31858
31934
|
}
|
|
31859
|
-
if (
|
|
31935
|
+
if (existsSync13(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
|
|
31860
31936
|
return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
|
|
31861
31937
|
}
|
|
31862
31938
|
const unseen = unseenByTheEngine(projectPath, to);
|
|
@@ -31864,7 +31940,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31864
31940
|
return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
|
|
31865
31941
|
}
|
|
31866
31942
|
if (!isDirectory) {
|
|
31867
|
-
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null &&
|
|
31943
|
+
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync13(join16(projectPath, path.slice("res://".length))));
|
|
31868
31944
|
if (owner !== undefined && owner !== null) {
|
|
31869
31945
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
31870
31946
|
}
|
|
@@ -31886,7 +31962,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31886
31962
|
} else {
|
|
31887
31963
|
moves.set(from, to);
|
|
31888
31964
|
for (const sidecar of SIDECARS) {
|
|
31889
|
-
if (
|
|
31965
|
+
if (existsSync13(`${absoluteFrom}${sidecar}`)) {
|
|
31890
31966
|
moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
|
|
31891
31967
|
}
|
|
31892
31968
|
}
|
|
@@ -31898,12 +31974,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31898
31974
|
scripts,
|
|
31899
31975
|
moves,
|
|
31900
31976
|
named: [from],
|
|
31901
|
-
exists: (path) =>
|
|
31977
|
+
exists: (path) => existsSync13(join16(projectPath, path.slice("res://".length)))
|
|
31902
31978
|
}, { edits: [], mentions: [] });
|
|
31903
31979
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
|
|
31904
31980
|
const binaries = [];
|
|
31905
31981
|
for (const path of binaryResources(projectPath)) {
|
|
31906
|
-
const bytes = readFileSync16(
|
|
31982
|
+
const bytes = readFileSync16(join16(projectPath, path.slice("res://".length)));
|
|
31907
31983
|
const names = [...moves.keys()].find((moving) => bytes.includes(moving));
|
|
31908
31984
|
if (names !== undefined) {
|
|
31909
31985
|
binaries.push({
|
|
@@ -31922,7 +31998,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31922
31998
|
}));
|
|
31923
31999
|
const left = leftAloneReport(mentions);
|
|
31924
32000
|
const rewrites = rewritesOf(plan, texts, null);
|
|
31925
|
-
const absoluteOf = (path) =>
|
|
32001
|
+
const absoluteOf = (path) => join16(projectPath, path.slice("res://".length));
|
|
31926
32002
|
const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
|
|
31927
32003
|
const moved = [...moves].map(([source, target]) => {
|
|
31928
32004
|
const rewrite = rewritten.get(source);
|
|
@@ -31969,19 +32045,19 @@ function applyFileMove(outcome) {
|
|
|
31969
32045
|
}
|
|
31970
32046
|
}
|
|
31971
32047
|
for (const file of outcome.moved) {
|
|
31972
|
-
if (!
|
|
32048
|
+
if (!existsSync13(file.absoluteFrom)) {
|
|
31973
32049
|
throw new Error(`${file.from} is gone from disk; nothing was written`);
|
|
31974
32050
|
}
|
|
31975
32051
|
if (file.text !== null && readFileSync16(file.absoluteFrom, "utf8") !== file.text.before) {
|
|
31976
32052
|
throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
|
|
31977
32053
|
}
|
|
31978
|
-
if (
|
|
32054
|
+
if (existsSync13(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
|
|
31979
32055
|
throw new Error(`${file.to} already exists; nothing was written`);
|
|
31980
32056
|
}
|
|
31981
32057
|
}
|
|
31982
32058
|
const undo = [];
|
|
31983
32059
|
const rename = (source, target) => {
|
|
31984
|
-
mkdirSync10(
|
|
32060
|
+
mkdirSync10(dirname12(target), { recursive: true });
|
|
31985
32061
|
renameSync3(source, target);
|
|
31986
32062
|
undo.push(() => {
|
|
31987
32063
|
renameSync3(target, source);
|
|
@@ -32704,27 +32780,27 @@ function getDefaultBridge(ownProject = null) {
|
|
|
32704
32780
|
}
|
|
32705
32781
|
|
|
32706
32782
|
// src/godot-path.ts
|
|
32707
|
-
import { existsSync as
|
|
32783
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
32708
32784
|
import { normalize } from "node:path";
|
|
32709
32785
|
|
|
32710
32786
|
// src/detection.ts
|
|
32711
|
-
import { existsSync as
|
|
32712
|
-
import { homedir as
|
|
32713
|
-
import { join as
|
|
32787
|
+
import { existsSync as existsSync14, readdirSync as readdirSync7, statSync as statSync8 } from "node:fs";
|
|
32788
|
+
import { homedir as homedir3 } from "node:os";
|
|
32789
|
+
import { join as join17 } from "node:path";
|
|
32714
32790
|
function resolveHomeDirectory() {
|
|
32715
32791
|
try {
|
|
32716
|
-
return
|
|
32792
|
+
return homedir3();
|
|
32717
32793
|
} catch {
|
|
32718
32794
|
return "";
|
|
32719
32795
|
}
|
|
32720
32796
|
}
|
|
32721
32797
|
function scanDirectoryForGodotBinaries(directory, platform) {
|
|
32722
|
-
if (!directory || !
|
|
32798
|
+
if (!directory || !existsSync14(directory)) {
|
|
32723
32799
|
return [];
|
|
32724
32800
|
}
|
|
32725
32801
|
let entries;
|
|
32726
32802
|
try {
|
|
32727
|
-
entries =
|
|
32803
|
+
entries = readdirSync7(directory);
|
|
32728
32804
|
} catch {
|
|
32729
32805
|
return [];
|
|
32730
32806
|
}
|
|
@@ -32734,16 +32810,16 @@ function scanDirectoryForGodotBinaries(directory, platform) {
|
|
|
32734
32810
|
if (!pattern.test(name)) {
|
|
32735
32811
|
continue;
|
|
32736
32812
|
}
|
|
32737
|
-
const fullPath =
|
|
32813
|
+
const fullPath = join17(directory, name);
|
|
32738
32814
|
try {
|
|
32739
|
-
const stat =
|
|
32815
|
+
const stat = statSync8(fullPath);
|
|
32740
32816
|
if (stat.isFile()) {
|
|
32741
32817
|
matches.push({ name, mtime: stat.mtimeMs });
|
|
32742
32818
|
}
|
|
32743
32819
|
} catch {}
|
|
32744
32820
|
}
|
|
32745
32821
|
matches.sort((a, b) => b.mtime - a.mtime);
|
|
32746
|
-
return matches.map((m) =>
|
|
32822
|
+
return matches.map((m) => join17(directory, m.name));
|
|
32747
32823
|
}
|
|
32748
32824
|
function conventionalPaths(platform, home) {
|
|
32749
32825
|
const paths = ["godot"];
|
|
@@ -32844,7 +32920,7 @@ class GodotLocator {
|
|
|
32844
32920
|
return known;
|
|
32845
32921
|
}
|
|
32846
32922
|
let reason;
|
|
32847
|
-
if (path !== "godot" && !
|
|
32923
|
+
if (path !== "godot" && !existsSync15(path)) {
|
|
32848
32924
|
reason = "does not exist";
|
|
32849
32925
|
} else {
|
|
32850
32926
|
try {
|
|
@@ -32860,12 +32936,12 @@ class GodotLocator {
|
|
|
32860
32936
|
}
|
|
32861
32937
|
|
|
32862
32938
|
// src/headless.ts
|
|
32863
|
-
import { existsSync as
|
|
32864
|
-
import { dirname as
|
|
32939
|
+
import { existsSync as existsSync19, readFileSync as readFileSync19, realpathSync as realpathSync5, writeFileSync as writeFileSync13 } from "node:fs";
|
|
32940
|
+
import { dirname as dirname14, join as join21 } from "node:path";
|
|
32865
32941
|
|
|
32866
32942
|
// src/library-copies.ts
|
|
32867
|
-
import { existsSync as
|
|
32868
|
-
import { basename as
|
|
32943
|
+
import { existsSync as existsSync16, readdirSync as readdirSync8, readFileSync as readFileSync17, renameSync as renameSync4, unlinkSync as unlinkSync3 } from "node:fs";
|
|
32944
|
+
import { basename as basename5, dirname as dirname13, isAbsolute as isAbsolute4, join as join18 } from "node:path";
|
|
32869
32945
|
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
32870
32946
|
const setAside = [];
|
|
32871
32947
|
const swept = [];
|
|
@@ -32873,26 +32949,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
32873
32949
|
return { setAside, swept };
|
|
32874
32950
|
}
|
|
32875
32951
|
for (const library of windowsLibraries(projectPath)) {
|
|
32876
|
-
const folder =
|
|
32877
|
-
const copy = `~${
|
|
32878
|
-
if (
|
|
32879
|
-
const aside =
|
|
32952
|
+
const folder = dirname13(library);
|
|
32953
|
+
const copy = `~${basename5(library)}`;
|
|
32954
|
+
if (existsSync16(join18(folder, copy))) {
|
|
32955
|
+
const aside = join18(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
|
|
32880
32956
|
try {
|
|
32881
|
-
renameSync4(
|
|
32957
|
+
renameSync4(join18(folder, copy), aside);
|
|
32882
32958
|
setAside.push(aside);
|
|
32883
32959
|
} catch {}
|
|
32884
32960
|
}
|
|
32885
32961
|
let names;
|
|
32886
32962
|
try {
|
|
32887
|
-
names =
|
|
32963
|
+
names = readdirSync8(folder);
|
|
32888
32964
|
} catch {
|
|
32889
32965
|
continue;
|
|
32890
32966
|
}
|
|
32891
32967
|
for (const name of names) {
|
|
32892
32968
|
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
32893
32969
|
try {
|
|
32894
|
-
unlinkSync3(
|
|
32895
|
-
swept.push(
|
|
32970
|
+
unlinkSync3(join18(folder, name));
|
|
32971
|
+
swept.push(join18(folder, name));
|
|
32896
32972
|
} catch {}
|
|
32897
32973
|
}
|
|
32898
32974
|
}
|
|
@@ -32900,7 +32976,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
32900
32976
|
return { setAside, swept };
|
|
32901
32977
|
}
|
|
32902
32978
|
function windowsLibraries(projectPath) {
|
|
32903
|
-
const listed = readOrNull(
|
|
32979
|
+
const listed = readOrNull(join18(projectPath, ".godot", "extension_list.cfg"));
|
|
32904
32980
|
if (listed === null) {
|
|
32905
32981
|
return [];
|
|
32906
32982
|
}
|
|
@@ -32919,7 +32995,7 @@ function windowsLibraries(projectPath) {
|
|
|
32919
32995
|
continue;
|
|
32920
32996
|
}
|
|
32921
32997
|
const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
|
|
32922
|
-
const library = named === undefined ? null : resolveResource(projectPath,
|
|
32998
|
+
const library = named === undefined ? null : resolveResource(projectPath, dirname13(manifest), named);
|
|
32923
32999
|
if (library !== null) {
|
|
32924
33000
|
libraries.add(library);
|
|
32925
33001
|
}
|
|
@@ -32932,9 +33008,9 @@ function resolveResource(projectPath, relativeTo, path) {
|
|
|
32932
33008
|
return null;
|
|
32933
33009
|
}
|
|
32934
33010
|
if (path.startsWith("res://")) {
|
|
32935
|
-
return
|
|
33011
|
+
return join18(projectPath, path.slice("res://".length));
|
|
32936
33012
|
}
|
|
32937
|
-
return
|
|
33013
|
+
return isAbsolute4(path) ? path : join18(relativeTo, path);
|
|
32938
33014
|
}
|
|
32939
33015
|
function readOrNull(path) {
|
|
32940
33016
|
try {
|
|
@@ -32946,34 +33022,35 @@ function readOrNull(path) {
|
|
|
32946
33022
|
|
|
32947
33023
|
// src/reimport.ts
|
|
32948
33024
|
import { createHash as createHash3 } from "node:crypto";
|
|
32949
|
-
import { existsSync as
|
|
32950
|
-
import { basename as
|
|
33025
|
+
import { existsSync as existsSync17, readFileSync as readFileSync18, rmSync as rmSync8, statSync as statSync9, utimesSync, writeFileSync as writeFileSync12 } from "node:fs";
|
|
33026
|
+
import { basename as basename6, join as join19 } from "node:path";
|
|
32951
33027
|
function forceNextImport(projectPath, resourcePath) {
|
|
32952
33028
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
32953
|
-
const sidecar =
|
|
32954
|
-
if (!
|
|
33029
|
+
const sidecar = join19(projectPath, `${relative}.import`);
|
|
33030
|
+
if (!existsSync17(sidecar)) {
|
|
32955
33031
|
return;
|
|
32956
33032
|
}
|
|
32957
|
-
const { atime, mtimeMs } =
|
|
33033
|
+
const { atime, mtimeMs } = statSync9(sidecar);
|
|
32958
33034
|
writeFileSync12(sidecar, readFileSync18(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
32959
33035
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
32960
33036
|
rmSync8(importRecord(projectPath, resourcePath), { force: true });
|
|
32961
33037
|
}
|
|
32962
33038
|
function importRecord(projectPath, resourcePath) {
|
|
32963
33039
|
const digest = createHash3("md5").update(resourcePath).digest("hex");
|
|
32964
|
-
return
|
|
33040
|
+
return join19(projectPath, ".godot", "imported", `${basename6(resourcePath)}-${digest}.md5`);
|
|
32965
33041
|
}
|
|
32966
33042
|
|
|
32967
33043
|
// src/scratch.ts
|
|
32968
|
-
import { existsSync as
|
|
33044
|
+
import { existsSync as existsSync18, mkdtempSync as mkdtempSync2, readdirSync as readdirSync9, rmSync as rmSync9 } from "node:fs";
|
|
32969
33045
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
32970
|
-
import { join as
|
|
32971
|
-
import
|
|
33046
|
+
import { join as join20 } from "node:path";
|
|
33047
|
+
import process8 from "node:process";
|
|
32972
33048
|
var SWEPT = { recursive: true, force: true };
|
|
32973
33049
|
var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
32974
33050
|
var waiting = new Set;
|
|
32975
33051
|
var atExit = new Set;
|
|
32976
33052
|
var KINDS = [
|
|
33053
|
+
"answers",
|
|
32977
33054
|
"desktop",
|
|
32978
33055
|
"export",
|
|
32979
33056
|
"import",
|
|
@@ -32982,12 +33059,12 @@ var KINDS = [
|
|
|
32982
33059
|
"tests"
|
|
32983
33060
|
];
|
|
32984
33061
|
function scratchDirectory(kind) {
|
|
32985
|
-
return mkdtempSync2(
|
|
33062
|
+
return mkdtempSync2(join20(tmpdir5(), `gdharness-${kind}-${process8.pid}-`));
|
|
32986
33063
|
}
|
|
32987
33064
|
function sweepAbandonedScratch(directory = tmpdir5()) {
|
|
32988
33065
|
let entries;
|
|
32989
33066
|
try {
|
|
32990
|
-
entries =
|
|
33067
|
+
entries = readdirSync9(directory);
|
|
32991
33068
|
} catch {
|
|
32992
33069
|
return;
|
|
32993
33070
|
}
|
|
@@ -32998,7 +33075,7 @@ function sweepAbandonedScratch(directory = tmpdir5()) {
|
|
|
32998
33075
|
continue;
|
|
32999
33076
|
}
|
|
33000
33077
|
try {
|
|
33001
|
-
remove(
|
|
33078
|
+
remove(join20(directory, entry));
|
|
33002
33079
|
} catch {}
|
|
33003
33080
|
}
|
|
33004
33081
|
}
|
|
@@ -33036,7 +33113,7 @@ function tryAgain(path, attempt) {
|
|
|
33036
33113
|
lastTryOnExit();
|
|
33037
33114
|
}
|
|
33038
33115
|
const after = AGAIN_AFTER_MS[attempt];
|
|
33039
|
-
if (after === undefined || !
|
|
33116
|
+
if (after === undefined || !existsSync18(path)) {
|
|
33040
33117
|
waiting.delete(path);
|
|
33041
33118
|
if (after === undefined) {
|
|
33042
33119
|
atExit.add(path);
|
|
@@ -33058,7 +33135,7 @@ function lastTryOnExit() {
|
|
|
33058
33135
|
return;
|
|
33059
33136
|
}
|
|
33060
33137
|
lastTryWatched = true;
|
|
33061
|
-
|
|
33138
|
+
process8.once("exit", () => {
|
|
33062
33139
|
for (const path of [...waiting, ...atExit]) {
|
|
33063
33140
|
try {
|
|
33064
33141
|
remove(path);
|
|
@@ -33078,8 +33155,14 @@ function snakeCased(params) {
|
|
|
33078
33155
|
}
|
|
33079
33156
|
function engineExtras(outcome) {
|
|
33080
33157
|
const after = outcome.afterAnswerMessages ?? [];
|
|
33158
|
+
const before = outcome.beforeOperationMessages ?? [];
|
|
33081
33159
|
return {
|
|
33082
33160
|
...outcome.messages.length > 0 ? { engine_messages: outcome.messages } : {},
|
|
33161
|
+
...before.length > 0 ? {
|
|
33162
|
+
engine_messages_before_operation: before,
|
|
33163
|
+
engine_messages_before_operation_note: BEFORE_OPERATION_NOTE
|
|
33164
|
+
} : {},
|
|
33165
|
+
...outcome.beforeOperationOmitted === undefined ? {} : { engine_messages_before_operation_omitted: outcome.beforeOperationOmitted },
|
|
33083
33166
|
...outcome.messagesOmitted === undefined ? {} : { engine_messages_omitted: outcome.messagesOmitted },
|
|
33084
33167
|
...after.length > 0 ? { engine_messages_after_answer: after, engine_messages_after_answer_note: AFTER_ANSWER_NOTE } : {},
|
|
33085
33168
|
...outcome.afterAnswerOmitted === undefined ? {} : { engine_messages_after_answer_omitted: outcome.afterAnswerOmitted },
|
|
@@ -33087,8 +33170,11 @@ function engineExtras(outcome) {
|
|
|
33087
33170
|
};
|
|
33088
33171
|
}
|
|
33089
33172
|
var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.";
|
|
33173
|
+
var BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.";
|
|
33090
33174
|
var OWN_ERROR = "[ERROR] ";
|
|
33091
33175
|
var ANSWERED = "[gdharness] answered";
|
|
33176
|
+
var WAITING = "[gdharness] waiting for the autoloads";
|
|
33177
|
+
var BEGUN = "[gdharness] begun";
|
|
33092
33178
|
var MESSAGES_KEPT = 200;
|
|
33093
33179
|
var ERRORS_NAMED = 3;
|
|
33094
33180
|
function problemsIn(log) {
|
|
@@ -33127,7 +33213,7 @@ function refusalsIn(log) {
|
|
|
33127
33213
|
}
|
|
33128
33214
|
function scriptFault(problems, script) {
|
|
33129
33215
|
const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
|
|
33130
|
-
const directories = [...new Set([spelled(
|
|
33216
|
+
const directories = [...new Set([spelled(dirname14(script)), spelled(resolvedOr(dirname14(script)))])];
|
|
33131
33217
|
return problems.find((entry) => {
|
|
33132
33218
|
const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
|
|
33133
33219
|
return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
|
|
@@ -33165,26 +33251,33 @@ async function runImport(godotPath, projectPath, options = {}) {
|
|
|
33165
33251
|
};
|
|
33166
33252
|
const first = await pass();
|
|
33167
33253
|
const collided = librariesNotCopied(first.messages);
|
|
33168
|
-
|
|
33254
|
+
const crash = first.docCacheNote;
|
|
33255
|
+
if (collided.length === 0 && crash === undefined) {
|
|
33169
33256
|
return first;
|
|
33170
33257
|
}
|
|
33171
|
-
const
|
|
33258
|
+
const { docCacheNote: _, ...again } = await pass();
|
|
33259
|
+
const second = again.ok || crash === undefined ? again : { ...again, message: `${again.message.replace(/[.\s]+$/, "")}. ${crash}` };
|
|
33172
33260
|
const still = librariesNotCopied(second.messages);
|
|
33173
33261
|
return {
|
|
33174
33262
|
...second,
|
|
33175
|
-
|
|
33176
|
-
|
|
33177
|
-
|
|
33263
|
+
...collided.length === 0 ? {} : {
|
|
33264
|
+
librariesRetried: collided,
|
|
33265
|
+
...still.length === 0 ? {} : { librariesNotLoaded: still },
|
|
33266
|
+
extensionNote: extensionRetryNote(collided, still)
|
|
33267
|
+
},
|
|
33268
|
+
...crash === undefined ? {} : { docCacheNote: crash }
|
|
33178
33269
|
};
|
|
33179
33270
|
}
|
|
33180
33271
|
async function importOnce(godotPath, projectPath) {
|
|
33181
33272
|
const logDir = scratchDirectory("import");
|
|
33273
|
+
const caches = editorCacheDirectory(godotPath);
|
|
33274
|
+
const cachesBefore = docCachesWritten(caches);
|
|
33182
33275
|
let ran;
|
|
33183
33276
|
try {
|
|
33184
33277
|
ran = await runEngine(godotPath, [
|
|
33185
33278
|
"--headless",
|
|
33186
33279
|
"--log-file",
|
|
33187
|
-
|
|
33280
|
+
join21(logDir, "engine.log"),
|
|
33188
33281
|
"--path",
|
|
33189
33282
|
projectPath,
|
|
33190
33283
|
"--import"
|
|
@@ -33193,24 +33286,26 @@ async function importOnce(godotPath, projectPath) {
|
|
|
33193
33286
|
discard(logDir);
|
|
33194
33287
|
}
|
|
33195
33288
|
const problems = problemsIn(ran.log);
|
|
33289
|
+
const crash = docCacheCrash(ran, cachesBefore, docCachesWritten(caches), "import");
|
|
33196
33290
|
if (ran.exitCode === 0 && ran.failure === null) {
|
|
33197
|
-
return { ok: true, ...reported2(problems) };
|
|
33291
|
+
return { ok: true, ...reported2(problems), ...crash === null ? {} : { docCacheNote: crash } };
|
|
33198
33292
|
}
|
|
33199
33293
|
return {
|
|
33200
33294
|
ok: false,
|
|
33201
33295
|
message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
|
|
33202
|
-
...reported2(problems)
|
|
33296
|
+
...reported2(problems),
|
|
33297
|
+
...crash === null ? {} : { docCacheNote: crash }
|
|
33203
33298
|
};
|
|
33204
33299
|
}
|
|
33205
33300
|
async function runOperation(engine, operation, params, projectPath) {
|
|
33206
33301
|
const paramsDir = scratchDirectory("params");
|
|
33207
|
-
const paramsFile =
|
|
33208
|
-
const answerFile =
|
|
33302
|
+
const paramsFile = join21(paramsDir, `${operation}.json`);
|
|
33303
|
+
const answerFile = join21(paramsDir, "answer.json");
|
|
33209
33304
|
writeFileSync13(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
33210
33305
|
const args = [
|
|
33211
33306
|
"--headless",
|
|
33212
33307
|
"--log-file",
|
|
33213
|
-
|
|
33308
|
+
join21(paramsDir, "engine.log"),
|
|
33214
33309
|
"--path",
|
|
33215
33310
|
projectPath,
|
|
33216
33311
|
"--script",
|
|
@@ -33229,12 +33324,22 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
33229
33324
|
discard(paramsDir);
|
|
33230
33325
|
}
|
|
33231
33326
|
const everything = ran.log.everything();
|
|
33232
|
-
const
|
|
33233
|
-
const
|
|
33327
|
+
const marked = (text) => everything.findIndex((entry) => entry.source === "stderr" && entry.text === text);
|
|
33328
|
+
const answered = marked(ANSWERED);
|
|
33329
|
+
const waiting = marked(WAITING);
|
|
33330
|
+
const begun = marked(BEGUN);
|
|
33331
|
+
const ended = answered === -1 ? everything.length : answered;
|
|
33332
|
+
const problems = waiting === -1 ? problemsAmong(everything.slice(0, ended)) : problemsAmong([
|
|
33333
|
+
...everything.slice(0, waiting),
|
|
33334
|
+
...begun === -1 ? [] : everything.slice(begun + 1, ended)
|
|
33335
|
+
]);
|
|
33336
|
+
const startup = waiting === -1 ? [] : problemsAmong(everything.slice(waiting + 1, begun === -1 ? ended : begun));
|
|
33234
33337
|
const after = answered === -1 ? [] : problemsAmong(everything.slice(answered + 1));
|
|
33235
33338
|
const afterAnswered = {
|
|
33236
33339
|
...after.length === 0 ? {} : { afterAnswerMessages: after.slice(-MESSAGES_KEPT) },
|
|
33237
|
-
...after.length > MESSAGES_KEPT ? { afterAnswerOmitted: after.length - MESSAGES_KEPT } : {}
|
|
33340
|
+
...after.length > MESSAGES_KEPT ? { afterAnswerOmitted: after.length - MESSAGES_KEPT } : {},
|
|
33341
|
+
...startup.length === 0 ? {} : { beforeOperationMessages: startup.slice(-MESSAGES_KEPT) },
|
|
33342
|
+
...startup.length > MESSAGES_KEPT ? { beforeOperationOmitted: startup.length - MESSAGES_KEPT } : {}
|
|
33238
33343
|
};
|
|
33239
33344
|
if (answer.kind === "unreadable") {
|
|
33240
33345
|
return {
|
|
@@ -33245,6 +33350,14 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
33245
33350
|
};
|
|
33246
33351
|
}
|
|
33247
33352
|
if (answer.kind === "none") {
|
|
33353
|
+
if (waiting !== -1 && begun === -1) {
|
|
33354
|
+
return {
|
|
33355
|
+
ok: false,
|
|
33356
|
+
message: `${operation} never ran: the engine ended (${howItEnded(ran)}) while it started the project's ` + `autoloads, which this operation waits for. The last it reported: ${reason(ran.log, startup)}`,
|
|
33357
|
+
...reported2(problems),
|
|
33358
|
+
...afterAnswered
|
|
33359
|
+
};
|
|
33360
|
+
}
|
|
33248
33361
|
const refusals = refusalsIn(ran.log);
|
|
33249
33362
|
if (refusals.length > 0) {
|
|
33250
33363
|
const said = new Set(refusals.map((entry) => entry.index));
|
|
@@ -33280,7 +33393,7 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
33280
33393
|
};
|
|
33281
33394
|
}
|
|
33282
33395
|
function answerIn(file) {
|
|
33283
|
-
if (!
|
|
33396
|
+
if (!existsSync19(file)) {
|
|
33284
33397
|
return { kind: "none" };
|
|
33285
33398
|
}
|
|
33286
33399
|
const text = readFileSync19(file, "utf8");
|
|
@@ -33686,6 +33799,31 @@ ${note}`;
|
|
|
33686
33799
|
return detail === entry.detail ? entry : { ...entry, detail };
|
|
33687
33800
|
});
|
|
33688
33801
|
}
|
|
33802
|
+
var MOST_REPORTS_SHOWN = 20;
|
|
33803
|
+
var LONGEST_DETAIL = 20000;
|
|
33804
|
+
function shortened(detail) {
|
|
33805
|
+
if (detail === null || detail.length <= LONGEST_DETAIL) {
|
|
33806
|
+
return detail;
|
|
33807
|
+
}
|
|
33808
|
+
return `${detail.slice(0, LONGEST_DETAIL)}
|
|
33809
|
+
[${detail.length - LONGEST_DETAIL} more characters left out]`;
|
|
33810
|
+
}
|
|
33811
|
+
function foldedReports(reports) {
|
|
33812
|
+
const first = new Map;
|
|
33813
|
+
for (const report of reports) {
|
|
33814
|
+
const key = JSON.stringify([report.kind, report.message, report.detail]);
|
|
33815
|
+
const seen = first.get(key);
|
|
33816
|
+
if (seen === undefined) {
|
|
33817
|
+
first.set(key, { report, times: 1 });
|
|
33818
|
+
} else {
|
|
33819
|
+
seen.times += 1;
|
|
33820
|
+
}
|
|
33821
|
+
}
|
|
33822
|
+
return [...first.values()].map(({ report, times }) => {
|
|
33823
|
+
const kept = { ...report, detail: shortened(report.detail) };
|
|
33824
|
+
return times === 1 ? kept : { ...kept, times };
|
|
33825
|
+
});
|
|
33826
|
+
}
|
|
33689
33827
|
function failedCases(report, printed) {
|
|
33690
33828
|
const cases = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ entry, path: suite.path })));
|
|
33691
33829
|
const read = withActualsPrinted(cases.flatMap(({ entry }) => entry.reports), printed);
|
|
@@ -33693,6 +33831,8 @@ function failedCases(report, printed) {
|
|
|
33693
33831
|
return cases.map(({ entry, path }) => {
|
|
33694
33832
|
const reports = read.slice(next, next + entry.reports.length);
|
|
33695
33833
|
next += entry.reports.length;
|
|
33834
|
+
const folded = foldedReports(reports);
|
|
33835
|
+
const shown = folded.slice(0, MOST_REPORTS_SHOWN);
|
|
33696
33836
|
return {
|
|
33697
33837
|
suite: entry.suite,
|
|
33698
33838
|
name: entry.name,
|
|
@@ -33700,11 +33840,34 @@ function failedCases(report, printed) {
|
|
|
33700
33840
|
time: entry.time,
|
|
33701
33841
|
path,
|
|
33702
33842
|
message: reports[0]?.message ?? null,
|
|
33703
|
-
detail: reports[0]?.detail ?? null,
|
|
33704
|
-
...reports.length > 1 ? { reports } : {}
|
|
33843
|
+
detail: shortened(reports[0]?.detail ?? null),
|
|
33844
|
+
...reports.length > 1 ? { reports: shown, reportCount: reports.length } : {},
|
|
33845
|
+
...folded.length > shown.length ? { reportsLeftOut: folded.length - shown.length } : {}
|
|
33705
33846
|
};
|
|
33706
33847
|
});
|
|
33707
33848
|
}
|
|
33849
|
+
function failedWithin(failed, budget) {
|
|
33850
|
+
const sizes = failed.map((entry) => JSON.stringify(entry).length);
|
|
33851
|
+
const smallestFirst = sizes.map((_, index) => index).sort((a, b) => (sizes[a] ?? 0) - (sizes[b] ?? 0));
|
|
33852
|
+
const whole = new Set;
|
|
33853
|
+
let spent = 0;
|
|
33854
|
+
for (const index of smallestFirst) {
|
|
33855
|
+
const size = sizes[index] ?? 0;
|
|
33856
|
+
if (spent + size > budget) {
|
|
33857
|
+
break;
|
|
33858
|
+
}
|
|
33859
|
+
spent += size;
|
|
33860
|
+
whole.add(index);
|
|
33861
|
+
}
|
|
33862
|
+
const kept = failed.map((entry, index) => {
|
|
33863
|
+
if (whole.has(index)) {
|
|
33864
|
+
return entry;
|
|
33865
|
+
}
|
|
33866
|
+
const { detail: _detail, reports: _reports, reportsLeftOut: _left, ...named } = entry;
|
|
33867
|
+
return { ...named, message: entry.message?.slice(0, 500) ?? null, detail: null };
|
|
33868
|
+
});
|
|
33869
|
+
return { failed: kept, briefed: failed.length - whole.size };
|
|
33870
|
+
}
|
|
33708
33871
|
var MASKED_BRACKET = "[lb]";
|
|
33709
33872
|
function masked(expected, actual) {
|
|
33710
33873
|
const allSpelled = (value) => !PRINTED_DICTIONARY.test(value) && value.split("[").length === value.split(MASKED_BRACKET).length;
|
|
@@ -33856,26 +34019,26 @@ function askTheKeeper(address, line, withinMs) {
|
|
|
33856
34019
|
}
|
|
33857
34020
|
|
|
33858
34021
|
// src/lifecycle-log.ts
|
|
33859
|
-
import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync5, statSync as
|
|
33860
|
-
import { dirname as
|
|
33861
|
-
import
|
|
34022
|
+
import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync5, statSync as statSync10 } from "node:fs";
|
|
34023
|
+
import { dirname as dirname15, join as join22 } from "node:path";
|
|
34024
|
+
import process9 from "node:process";
|
|
33862
34025
|
function lifecycleLogPath() {
|
|
33863
|
-
return
|
|
34026
|
+
return join22(runtimeDirectory(), "servers.log");
|
|
33864
34027
|
}
|
|
33865
34028
|
var LIFECYCLE_LOG_LIMIT = 1e6;
|
|
33866
34029
|
function writeLifecycle(event, fields = {}) {
|
|
33867
34030
|
const path = lifecycleLogPath();
|
|
33868
34031
|
try {
|
|
33869
|
-
mkdirSync11(
|
|
33870
|
-
if ((
|
|
34032
|
+
mkdirSync11(dirname15(path), { recursive: true });
|
|
34033
|
+
if ((statSync10(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
33871
34034
|
try {
|
|
33872
34035
|
renameSync5(path, `${path}.1`);
|
|
33873
34036
|
} catch {}
|
|
33874
34037
|
}
|
|
33875
34038
|
const line = {
|
|
33876
34039
|
at: new Date().toISOString(),
|
|
33877
|
-
pid:
|
|
33878
|
-
ppid:
|
|
34040
|
+
pid: process9.pid,
|
|
34041
|
+
ppid: process9.ppid,
|
|
33879
34042
|
version: SERVER_VERSION,
|
|
33880
34043
|
event
|
|
33881
34044
|
};
|
|
@@ -34164,14 +34327,14 @@ function setupPromptHandlers(server) {
|
|
|
34164
34327
|
|
|
34165
34328
|
// src/restart-note.ts
|
|
34166
34329
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
34167
|
-
import { dirname as
|
|
34330
|
+
import { dirname as dirname16, join as join23 } from "node:path";
|
|
34168
34331
|
function restartNotePath(projectPath) {
|
|
34169
|
-
return
|
|
34332
|
+
return join23(projectPath, ".godot", "gdharness-restart.json");
|
|
34170
34333
|
}
|
|
34171
34334
|
function noteRestartBegun(note) {
|
|
34172
34335
|
const path = restartNotePath(note.projectPath);
|
|
34173
34336
|
try {
|
|
34174
|
-
mkdirSync12(
|
|
34337
|
+
mkdirSync12(dirname16(path), { recursive: true });
|
|
34175
34338
|
writeFileSync14(path, `${JSON.stringify(note, null, 2)}
|
|
34176
34339
|
`, "utf8");
|
|
34177
34340
|
} catch (error) {
|
|
@@ -34203,23 +34366,23 @@ function restartSettled(projectPath) {
|
|
|
34203
34366
|
|
|
34204
34367
|
// src/run-record.ts
|
|
34205
34368
|
import { createHash as createHash4 } from "node:crypto";
|
|
34206
|
-
import { mkdirSync as mkdirSync13, openSync as openSync3, readdirSync as
|
|
34207
|
-
import { basename as
|
|
34369
|
+
import { mkdirSync as mkdirSync13, openSync as openSync3, readdirSync as readdirSync10, readFileSync as readFileSync21, realpathSync as realpathSync6, rmSync as rmSync11, statSync as statSync11 } from "node:fs";
|
|
34370
|
+
import { basename as basename7, dirname as dirname17, join as join24, resolve as resolve6 } from "node:path";
|
|
34208
34371
|
function runsDirectory() {
|
|
34209
|
-
return
|
|
34372
|
+
return join24(runtimeDirectory(), "runs");
|
|
34210
34373
|
}
|
|
34211
34374
|
function realAsFarAsItExists(path) {
|
|
34212
34375
|
const missing = [];
|
|
34213
34376
|
let at = path;
|
|
34214
34377
|
for (;; ) {
|
|
34215
34378
|
try {
|
|
34216
|
-
return
|
|
34379
|
+
return join24(realpathSync6.native(at), ...missing);
|
|
34217
34380
|
} catch {
|
|
34218
|
-
const parent =
|
|
34381
|
+
const parent = dirname17(at);
|
|
34219
34382
|
if (parent === at) {
|
|
34220
34383
|
return path;
|
|
34221
34384
|
}
|
|
34222
|
-
missing.unshift(
|
|
34385
|
+
missing.unshift(basename7(at));
|
|
34223
34386
|
at = parent;
|
|
34224
34387
|
}
|
|
34225
34388
|
}
|
|
@@ -34230,10 +34393,10 @@ function projectKey(projectPath) {
|
|
|
34230
34393
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
34231
34394
|
}
|
|
34232
34395
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
34233
|
-
return
|
|
34396
|
+
return join24(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
34234
34397
|
}
|
|
34235
34398
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
34236
|
-
return
|
|
34399
|
+
return join24(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
34237
34400
|
}
|
|
34238
34401
|
function writeLatestRun(servedBy, latest) {
|
|
34239
34402
|
try {
|
|
@@ -34259,17 +34422,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
34259
34422
|
var SHARED_NOTE = "run.json";
|
|
34260
34423
|
function openTranscript(startedAt) {
|
|
34261
34424
|
mkdirSync13(runsDirectory(), { recursive: true });
|
|
34262
|
-
const path =
|
|
34425
|
+
const path = join24(runsDirectory(), `run-${startedAt}.log`);
|
|
34263
34426
|
return { path, fd: openSync3(path, "a") };
|
|
34264
34427
|
}
|
|
34265
34428
|
function profilePath(startedAt) {
|
|
34266
|
-
return
|
|
34429
|
+
return join24(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
34430
|
+
}
|
|
34431
|
+
function profileMarkPath(startedAt) {
|
|
34432
|
+
return join24(runsDirectory(), `run-${startedAt}.profile-mark.json`);
|
|
34267
34433
|
}
|
|
34268
34434
|
function noteIsAbout(note, playing) {
|
|
34269
34435
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
34270
34436
|
}
|
|
34271
34437
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
34272
|
-
return
|
|
34438
|
+
return join24(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
34273
34439
|
}
|
|
34274
34440
|
function writeEditorRunNote(note) {
|
|
34275
34441
|
mkdirSync13(runsDirectory(), { recursive: true });
|
|
@@ -34280,7 +34446,7 @@ function writeEditorRunNote(note) {
|
|
|
34280
34446
|
}
|
|
34281
34447
|
function readEditorRunNote(projectPath) {
|
|
34282
34448
|
for (const directory of runtimeDirectories()) {
|
|
34283
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
34449
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join24(directory, "runs", "editor-run.json"));
|
|
34284
34450
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
34285
34451
|
return note;
|
|
34286
34452
|
}
|
|
@@ -34314,7 +34480,7 @@ function readRunRecord(projectPath) {
|
|
|
34314
34480
|
if (own !== null) {
|
|
34315
34481
|
return own;
|
|
34316
34482
|
}
|
|
34317
|
-
const shared = recordAt(
|
|
34483
|
+
const shared = recordAt(join24(directory, "runs", SHARED_NOTE));
|
|
34318
34484
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
34319
34485
|
return shared;
|
|
34320
34486
|
}
|
|
@@ -34326,13 +34492,13 @@ function everyRunRecord() {
|
|
|
34326
34492
|
for (const directory of runtimeDirectories()) {
|
|
34327
34493
|
let entries;
|
|
34328
34494
|
try {
|
|
34329
|
-
entries =
|
|
34495
|
+
entries = readdirSync10(join24(directory, "runs"));
|
|
34330
34496
|
} catch {
|
|
34331
34497
|
continue;
|
|
34332
34498
|
}
|
|
34333
34499
|
for (const entry of entries) {
|
|
34334
34500
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
34335
|
-
const record = recordAt(
|
|
34501
|
+
const record = recordAt(join24(directory, "runs", entry));
|
|
34336
34502
|
if (record !== null) {
|
|
34337
34503
|
found.push(record);
|
|
34338
34504
|
}
|
|
@@ -34502,7 +34668,7 @@ function whyNotTheRun(record, running, needed) {
|
|
|
34502
34668
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
34503
34669
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
34504
34670
|
}
|
|
34505
|
-
const engine = record.command === undefined ? null :
|
|
34671
|
+
const engine = record.command === undefined ? null : basename7(record.command);
|
|
34506
34672
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
34507
34673
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
34508
34674
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -34541,7 +34707,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
34541
34707
|
}
|
|
34542
34708
|
function clearRunRecord(projectPath) {
|
|
34543
34709
|
for (const directory of runtimeDirectories()) {
|
|
34544
|
-
const shared =
|
|
34710
|
+
const shared = join24(directory, "runs", SHARED_NOTE);
|
|
34545
34711
|
const sharedRecord = recordAt(shared);
|
|
34546
34712
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
34547
34713
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -34553,7 +34719,7 @@ function clearRunRecord(projectPath) {
|
|
|
34553
34719
|
}
|
|
34554
34720
|
function ageMs(path, now) {
|
|
34555
34721
|
try {
|
|
34556
|
-
return now -
|
|
34722
|
+
return now - statSync11(path).mtimeMs;
|
|
34557
34723
|
} catch {
|
|
34558
34724
|
return 0;
|
|
34559
34725
|
}
|
|
@@ -34562,20 +34728,21 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
34562
34728
|
const directory = runsDirectory();
|
|
34563
34729
|
const kept = new Set(everyRunRecord().flatMap((record) => [
|
|
34564
34730
|
record.transcript,
|
|
34731
|
+
profileMarkPath(record.startedAt),
|
|
34565
34732
|
...record.profile === undefined ? [] : [record.profile]
|
|
34566
34733
|
]));
|
|
34567
34734
|
let entries;
|
|
34568
34735
|
try {
|
|
34569
|
-
entries =
|
|
34736
|
+
entries = readdirSync10(directory);
|
|
34570
34737
|
} catch {
|
|
34571
34738
|
return;
|
|
34572
34739
|
}
|
|
34573
34740
|
for (const entry of entries) {
|
|
34574
|
-
const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json"));
|
|
34741
|
+
const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json") || entry.endsWith(".profile-mark.json"));
|
|
34575
34742
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
34576
34743
|
continue;
|
|
34577
34744
|
}
|
|
34578
|
-
const path =
|
|
34745
|
+
const path = join24(directory, entry);
|
|
34579
34746
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
34580
34747
|
continue;
|
|
34581
34748
|
}
|
|
@@ -34586,7 +34753,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
34586
34753
|
}
|
|
34587
34754
|
|
|
34588
34755
|
// src/script-origins.ts
|
|
34589
|
-
import { join as
|
|
34756
|
+
import { join as join25 } from "node:path";
|
|
34590
34757
|
var UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
|
|
34591
34758
|
function unresolvedClassIn(message) {
|
|
34592
34759
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
@@ -34642,7 +34809,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
34642
34809
|
const ran = await runEngine(godotPath, [
|
|
34643
34810
|
"--headless",
|
|
34644
34811
|
"--log-file",
|
|
34645
|
-
|
|
34812
|
+
join25(logDir, "engine.log"),
|
|
34646
34813
|
"--path",
|
|
34647
34814
|
projectPath,
|
|
34648
34815
|
"--check-only",
|
|
@@ -34736,6 +34903,52 @@ function isCalibration(signature) {
|
|
|
34736
34903
|
const parts = signatureParts(signature);
|
|
34737
34904
|
return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
|
|
34738
34905
|
}
|
|
34906
|
+
function writeProfileMark(path, mark) {
|
|
34907
|
+
return writeWhole(path, JSON.stringify(mark));
|
|
34908
|
+
}
|
|
34909
|
+
function readProfileMark(path) {
|
|
34910
|
+
try {
|
|
34911
|
+
const parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
34912
|
+
return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
|
|
34913
|
+
} catch {
|
|
34914
|
+
return null;
|
|
34915
|
+
}
|
|
34916
|
+
}
|
|
34917
|
+
function totalsSince(now, mark) {
|
|
34918
|
+
if (now.frames < mark.frames || now.startedAt !== mark.startedAt) {
|
|
34919
|
+
return null;
|
|
34920
|
+
}
|
|
34921
|
+
let shrunk = 0;
|
|
34922
|
+
const functions = {};
|
|
34923
|
+
for (const [signature, sum] of Object.entries(now.functions)) {
|
|
34924
|
+
const before = mark.functions[signature];
|
|
34925
|
+
if (isCalibration(signature) || before === undefined) {
|
|
34926
|
+
functions[signature] = sum;
|
|
34927
|
+
continue;
|
|
34928
|
+
}
|
|
34929
|
+
const added = {
|
|
34930
|
+
calls: sum.calls - before.calls,
|
|
34931
|
+
selfSeconds: sum.selfSeconds - before.selfSeconds,
|
|
34932
|
+
totalSeconds: sum.totalSeconds - before.totalSeconds
|
|
34933
|
+
};
|
|
34934
|
+
if (added.calls < 0 || added.selfSeconds < 0 || added.totalSeconds < 0) {
|
|
34935
|
+
shrunk += 1;
|
|
34936
|
+
} else if (added.calls > 0) {
|
|
34937
|
+
functions[signature] = added;
|
|
34938
|
+
}
|
|
34939
|
+
}
|
|
34940
|
+
shrunk += Object.keys(mark.functions).filter((signature) => now.functions[signature] === undefined && (mark.functions[signature]?.calls ?? 0) > 0).length;
|
|
34941
|
+
return {
|
|
34942
|
+
totals: {
|
|
34943
|
+
...now,
|
|
34944
|
+
frames: now.frames - mark.frames,
|
|
34945
|
+
unreadable: Math.max(0, now.unreadable - mark.unreadable),
|
|
34946
|
+
...now.cappedFrames === undefined ? {} : { cappedFrames: Math.max(0, now.cappedFrames - (mark.cappedFrames ?? 0)) },
|
|
34947
|
+
functions
|
|
34948
|
+
},
|
|
34949
|
+
shrunk
|
|
34950
|
+
};
|
|
34951
|
+
}
|
|
34739
34952
|
function perCallCostOf(totals) {
|
|
34740
34953
|
for (const [signature, sum] of Object.entries(totals.functions)) {
|
|
34741
34954
|
const parts = signatureParts(signature);
|
|
@@ -34899,6 +35112,8 @@ var SCENE_PATH = {
|
|
|
34899
35112
|
type: "string",
|
|
34900
35113
|
description: 'Scene file inside the project, such as "scenes/main.tscn" or "res://scenes/main.tscn".'
|
|
34901
35114
|
};
|
|
35115
|
+
var WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. An editor of the project that is running and has not reached this server is waited for rather than written around.";
|
|
35116
|
+
var WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.';
|
|
34902
35117
|
var SCRIPT_PATH = {
|
|
34903
35118
|
type: "string",
|
|
34904
35119
|
description: 'Script file inside the project, such as "scripts/player.gd".'
|
|
@@ -34911,7 +35126,7 @@ var NODE_PATH = {
|
|
|
34911
35126
|
};
|
|
34912
35127
|
var PROPERTIES = {
|
|
34913
35128
|
type: "object",
|
|
34914
|
-
description:
|
|
35129
|
+
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 key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y"; a component the value does not have is refused, naming the ones it has. 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. ${WHOLE_NUMBERS}`,
|
|
34915
35130
|
additionalProperties: true
|
|
34916
35131
|
};
|
|
34917
35132
|
var XY = {
|
|
@@ -34955,7 +35170,10 @@ var SCRIPT_MODIFICATIONS = {
|
|
|
34955
35170
|
name: { type: "string" },
|
|
34956
35171
|
params: { type: "string", description: 'Functions and signals: the parameter list, "delta: float".' },
|
|
34957
35172
|
returnType: { type: "string", description: "Functions: the return type." },
|
|
34958
|
-
body: {
|
|
35173
|
+
body: {
|
|
35174
|
+
type: "string",
|
|
35175
|
+
description: "Functions: the body, its lines separated by \\n. Indent shared by every line is taken off and one tab put on, so it can be written indented or not."
|
|
35176
|
+
},
|
|
34959
35177
|
varType: {
|
|
34960
35178
|
type: "string",
|
|
34961
35179
|
description: "Variables and constants: the type annotation; without one, the type the value evaluates to."
|
|
@@ -35058,7 +35276,7 @@ var TOOL_SPECS = [
|
|
|
35058
35276
|
{
|
|
35059
35277
|
name: "project_settings",
|
|
35060
35278
|
title: "Project settings",
|
|
35061
|
-
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change
|
|
35279
|
+
description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, one on an addon from before this, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change moves no other line; the save is the engine's, and like the editor's it writes Godot's header and config/features into a file that lacks them. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
35062
35280
|
parameters: {
|
|
35063
35281
|
projectPath: PROJECT_PATH2,
|
|
35064
35282
|
setting: {
|
|
@@ -35236,24 +35454,32 @@ var TOOL_SPECS = [
|
|
|
35236
35454
|
description: "The import pipeline: what needs importing, how a resource is imported, reimports, UIDs, and the global class list the editor and the engine read.",
|
|
35237
35455
|
parameters: {
|
|
35238
35456
|
projectPath: PROJECT_PATH2,
|
|
35239
|
-
resourcePath: RESOURCE_PATH,
|
|
35457
|
+
resourcePath: { ...RESOURCE_PATH, ops: ["status", "options", "set_options", "uid", "reimport"] },
|
|
35240
35458
|
includeUpToDate: {
|
|
35241
35459
|
type: "boolean",
|
|
35460
|
+
ops: ["status"],
|
|
35242
35461
|
description: "status: list resources that are current as well. Default false."
|
|
35243
35462
|
},
|
|
35244
35463
|
options: {
|
|
35245
35464
|
type: "object",
|
|
35465
|
+
ops: ["set_options"],
|
|
35246
35466
|
description: `set_options: import options keyed as the .import file spells them, {"compress/mode": 1}. A dictionary option whose keys are numbers, a font's opentype_features or any option already holding number keys, takes them written as strings of digits, and opentype_features also takes an OpenType tag by its four-letter name: {"opentype_features": {"liga": 0}} turns ligatures off. Both are written as the numbers the engine reads.`,
|
|
35247
35467
|
additionalProperties: true
|
|
35248
35468
|
},
|
|
35249
35469
|
reimport: {
|
|
35250
35470
|
type: "boolean",
|
|
35471
|
+
ops: ["set_options"],
|
|
35251
35472
|
description: "set_options: reimport afterwards, answered under reimport as project_import reimport answers. Default true."
|
|
35252
35473
|
},
|
|
35253
|
-
force: {
|
|
35474
|
+
force: {
|
|
35475
|
+
type: "boolean",
|
|
35476
|
+
ops: ["reimport"],
|
|
35477
|
+
description: "reimport: reimport even what is current. Default false."
|
|
35478
|
+
},
|
|
35254
35479
|
timeoutMs: {
|
|
35255
35480
|
type: "integer",
|
|
35256
35481
|
minimum: 1,
|
|
35482
|
+
ops: ["reimport", "set_options"],
|
|
35257
35483
|
description: "reimport, set_options: how long to wait for the editor to finish reimporting. Default 300000."
|
|
35258
35484
|
}
|
|
35259
35485
|
},
|
|
@@ -35331,7 +35557,7 @@ var TOOL_SPECS = [
|
|
|
35331
35557
|
name: "project_test",
|
|
35332
35558
|
title: "Run tests",
|
|
35333
35559
|
effect: "additive",
|
|
35334
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one,
|
|
35560
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
35335
35561
|
parameters: {
|
|
35336
35562
|
projectPath: PROJECT_PATH2,
|
|
35337
35563
|
path: {
|
|
@@ -35368,14 +35594,14 @@ var TOOL_SPECS = [
|
|
|
35368
35594
|
{
|
|
35369
35595
|
name: "scene_create",
|
|
35370
35596
|
title: "Create or save a scene",
|
|
35371
|
-
description:
|
|
35597
|
+
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. ${WITHOUT_AN_EDITOR}`,
|
|
35372
35598
|
parameters: {
|
|
35373
35599
|
projectPath: PROJECT_PATH2,
|
|
35374
35600
|
scenePath: SCENE_PATH,
|
|
35375
35601
|
rootNodeType: {
|
|
35376
35602
|
type: "string",
|
|
35377
35603
|
ops: ["create"],
|
|
35378
|
-
description: "create: the root node class. Default Node."
|
|
35604
|
+
description: "create: the root node class, an engine class or a class_name a project script declares, which the root is made as with that script attached. Default Node."
|
|
35379
35605
|
},
|
|
35380
35606
|
rootNodeName: {
|
|
35381
35607
|
type: "string",
|
|
@@ -35413,29 +35639,32 @@ var TOOL_SPECS = [
|
|
|
35413
35639
|
{
|
|
35414
35640
|
name: "scene_node",
|
|
35415
35641
|
title: "Scene nodes",
|
|
35416
|
-
description:
|
|
35642
|
+
description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. 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. ${WITHOUT_AN_EDITOR}`,
|
|
35417
35643
|
parameters: {
|
|
35418
35644
|
projectPath: PROJECT_PATH2,
|
|
35419
35645
|
scenePath: SCENE_PATH,
|
|
35420
|
-
nodePath: NODE_PATH,
|
|
35646
|
+
nodePath: { ...NODE_PATH, ops: ["get", "set", "duplicate", "reparent", "delete", "set_tilemap_cells"] },
|
|
35421
35647
|
parentNodePath: {
|
|
35422
35648
|
type: "string",
|
|
35649
|
+
ops: ["add", "duplicate"],
|
|
35423
35650
|
description: "add: where the node goes. Default the root. duplicate: where the copy goes."
|
|
35424
35651
|
},
|
|
35425
35652
|
nodeType: {
|
|
35426
35653
|
type: "string",
|
|
35427
|
-
|
|
35654
|
+
ops: ["add"],
|
|
35655
|
+
description: 'add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath.'
|
|
35428
35656
|
},
|
|
35429
35657
|
instancePath: {
|
|
35430
35658
|
type: "string",
|
|
35431
35659
|
ops: ["add"],
|
|
35432
35660
|
description: `add: a scene to instance as the new node instead of a class, such as "ui/panel.tscn". It is saved as an instance of that file, the way the editor's Instantiate Child Scene saves one, so changes to that scene reach it; properties set its root. A scene that is the target or instances it is refused.`
|
|
35433
35661
|
},
|
|
35434
|
-
nodeName: { type: "string", description: "add: the new node's name." },
|
|
35435
|
-
properties: PROPERTIES,
|
|
35436
|
-
newName: { type: "string", description: "duplicate: the copy's name." },
|
|
35662
|
+
nodeName: { type: "string", ops: ["add"], description: "add: the new node's name." },
|
|
35663
|
+
properties: { ...PROPERTIES, ops: ["add", "set"] },
|
|
35664
|
+
newName: { type: "string", ops: ["duplicate"], description: "duplicate: the copy's name." },
|
|
35437
35665
|
newParentPath: {
|
|
35438
35666
|
type: "string",
|
|
35667
|
+
ops: ["reparent"],
|
|
35439
35668
|
description: "reparent: the new parent, which may be the one it has, to move it among its siblings."
|
|
35440
35669
|
},
|
|
35441
35670
|
index: {
|
|
@@ -35446,13 +35675,15 @@ var TOOL_SPECS = [
|
|
|
35446
35675
|
},
|
|
35447
35676
|
includeDefaults: {
|
|
35448
35677
|
type: "boolean",
|
|
35678
|
+
ops: ["get"],
|
|
35449
35679
|
description: "get: include properties still at their default. Default false."
|
|
35450
35680
|
},
|
|
35451
35681
|
layer: {
|
|
35452
35682
|
type: "number",
|
|
35683
|
+
ops: ["set_tilemap_cells"],
|
|
35453
35684
|
description: "set_tilemap_cells: the layer of a TileMap. Default 0. A TileMapLayer is one layer."
|
|
35454
35685
|
},
|
|
35455
|
-
cells: TILEMAP_CELLS
|
|
35686
|
+
cells: { ...TILEMAP_CELLS, ops: ["set_tilemap_cells"] }
|
|
35456
35687
|
},
|
|
35457
35688
|
requires: ["projectPath", "scenePath"],
|
|
35458
35689
|
operations: {
|
|
@@ -35492,16 +35723,32 @@ var TOOL_SPECS = [
|
|
|
35492
35723
|
{
|
|
35493
35724
|
name: "scene_signal",
|
|
35494
35725
|
title: "Scene signals",
|
|
35495
|
-
description:
|
|
35726
|
+
description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connect of a pair already connected says alreadyConnected, with flagsBefore when it changed the flags. ${WITHOUT_AN_EDITOR}`,
|
|
35496
35727
|
parameters: {
|
|
35497
35728
|
projectPath: PROJECT_PATH2,
|
|
35498
35729
|
scenePath: SCENE_PATH,
|
|
35499
|
-
sourceNodePath: { type: "string", description: "The node that emits." },
|
|
35500
|
-
signalName: {
|
|
35501
|
-
|
|
35502
|
-
|
|
35503
|
-
|
|
35504
|
-
|
|
35730
|
+
sourceNodePath: { type: "string", ops: ["connect", "disconnect"], description: "The node that emits." },
|
|
35731
|
+
signalName: {
|
|
35732
|
+
type: "string",
|
|
35733
|
+
ops: ["connect", "disconnect"],
|
|
35734
|
+
description: 'The signal it emits, such as "pressed".'
|
|
35735
|
+
},
|
|
35736
|
+
targetNodePath: {
|
|
35737
|
+
type: "string",
|
|
35738
|
+
ops: ["connect", "disconnect"],
|
|
35739
|
+
description: "The node whose method is called."
|
|
35740
|
+
},
|
|
35741
|
+
methodName: {
|
|
35742
|
+
type: "string",
|
|
35743
|
+
ops: ["connect", "disconnect"],
|
|
35744
|
+
description: "The method on that node, called when it emits."
|
|
35745
|
+
},
|
|
35746
|
+
flags: {
|
|
35747
|
+
type: "number",
|
|
35748
|
+
ops: ["connect"],
|
|
35749
|
+
description: "connect: Object.ConnectFlags, such as 1 for deferred."
|
|
35750
|
+
},
|
|
35751
|
+
nodePath: { type: "string", ops: ["list"], description: "list: only connections involving this node." }
|
|
35505
35752
|
},
|
|
35506
35753
|
requires: ["projectPath", "scenePath"],
|
|
35507
35754
|
operations: {
|
|
@@ -35626,18 +35873,28 @@ var TOOL_SPECS = [
|
|
|
35626
35873
|
parameters: {
|
|
35627
35874
|
projectPath: PROJECT_PATH2,
|
|
35628
35875
|
scriptPath: SCRIPT_PATH,
|
|
35629
|
-
className: {
|
|
35630
|
-
|
|
35876
|
+
className: {
|
|
35877
|
+
type: "string",
|
|
35878
|
+
ops: ["create"],
|
|
35879
|
+
description: "create: a class_name for the script. Not with content."
|
|
35880
|
+
},
|
|
35881
|
+
extends: {
|
|
35882
|
+
type: "string",
|
|
35883
|
+
ops: ["create"],
|
|
35884
|
+
description: "create: the base class. Default Node. Not with content."
|
|
35885
|
+
},
|
|
35631
35886
|
content: {
|
|
35632
35887
|
type: "string",
|
|
35888
|
+
ops: ["create"],
|
|
35633
35889
|
description: "create: the whole file, written as given, instead of a template, className and extends."
|
|
35634
35890
|
},
|
|
35635
35891
|
template: {
|
|
35636
35892
|
type: "string",
|
|
35637
35893
|
enum: ["singleton", "state_machine", "component", "resource"],
|
|
35894
|
+
ops: ["create"],
|
|
35638
35895
|
description: "create: a starting shape."
|
|
35639
35896
|
},
|
|
35640
|
-
modifications: SCRIPT_MODIFICATIONS,
|
|
35897
|
+
modifications: { ...SCRIPT_MODIFICATIONS, ops: ["modify"] },
|
|
35641
35898
|
symbol: {
|
|
35642
35899
|
type: "string",
|
|
35643
35900
|
ops: ["rename"],
|
|
@@ -35675,16 +35932,19 @@ var TOOL_SPECS = [
|
|
|
35675
35932
|
scriptPath: SCRIPT_PATH,
|
|
35676
35933
|
includeInherited: {
|
|
35677
35934
|
type: "boolean",
|
|
35935
|
+
ops: ["structure"],
|
|
35678
35936
|
description: "structure: also list what the script inherits, walking extends through the project's other scripts. Each such member carries inherited_from, the file declaring it, and the answer gains inherits_from, the chain that was walked. A name the script overrides is listed at both its lines rather than once. A native base is not walked, since it declares nothing in a file: editor_classes info answers for those. Default false."
|
|
35679
35937
|
},
|
|
35680
35938
|
line: {
|
|
35681
35939
|
type: "integer",
|
|
35682
35940
|
minimum: 1,
|
|
35941
|
+
ops: ["completion", "hover"],
|
|
35683
35942
|
description: "completion, hover: the line, counted from 1 as the editor shows it."
|
|
35684
35943
|
},
|
|
35685
35944
|
character: {
|
|
35686
35945
|
type: "integer",
|
|
35687
35946
|
minimum: 1,
|
|
35947
|
+
ops: ["completion", "hover"],
|
|
35688
35948
|
description: "completion, hover: the column, counted from 1 as the editor shows it, with a tab counted as one. symbols gives each name at the line and column hover is asked at."
|
|
35689
35949
|
}
|
|
35690
35950
|
},
|
|
@@ -35692,7 +35952,7 @@ var TOOL_SPECS = [
|
|
|
35692
35952
|
operations: {
|
|
35693
35953
|
structure: {
|
|
35694
35954
|
effect: "read",
|
|
35695
|
-
summary: "functions, variables, signals, class_name and extends, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class",
|
|
35955
|
+
summary: "functions, variables, signals, class_name and extends, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies",
|
|
35696
35956
|
requires: []
|
|
35697
35957
|
},
|
|
35698
35958
|
symbols: {
|
|
@@ -35727,7 +35987,7 @@ var TOOL_SPECS = [
|
|
|
35727
35987
|
{
|
|
35728
35988
|
name: "resource_edit",
|
|
35729
35989
|
title: "Edit resources",
|
|
35730
|
-
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource
|
|
35990
|
+
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, and a Theme lists its items there as "Button/colors/font_color". move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before the save, and a resource where that cannot be done is refused and the file left as it was.',
|
|
35731
35991
|
parameters: {
|
|
35732
35992
|
projectPath: PROJECT_PATH2,
|
|
35733
35993
|
resourcePath: {
|
|
@@ -35737,7 +35997,7 @@ var TOOL_SPECS = [
|
|
|
35737
35997
|
resourceType: {
|
|
35738
35998
|
type: "string",
|
|
35739
35999
|
ops: ["create"],
|
|
35740
|
-
description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D".'
|
|
36000
|
+
description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D", or a class_name a project script declares, which the resource is made as with that script attached.'
|
|
35741
36001
|
},
|
|
35742
36002
|
properties: { ...PROPERTIES, ops: ["create", "modify"] },
|
|
35743
36003
|
script: {
|
|
@@ -35931,6 +36191,17 @@ var TOOL_SPECS = [
|
|
|
35931
36191
|
ops: ["profile"],
|
|
35932
36192
|
description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed. A property's get or set block, which the profile names Class.@name_getter or Class.@name_setter, is asked about by that name or by the property's: the places are where the property is named, each saying instead of called whether it writes the property (access write), which runs the set block, or reads it (access read), which runs the get block, and the rows are both blocks'. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not places."
|
|
35933
36193
|
},
|
|
36194
|
+
mark: {
|
|
36195
|
+
type: "boolean",
|
|
36196
|
+
ops: ["profile"],
|
|
36197
|
+
description: `profile: answer as usual and set the run's mark at these totals, so a later profile with since: "mark" answers for one action, a press or a commit, apart from the boot and everything before it. The mark is kept beside the run's profile, so a server that replaces this one reads it too, and setting it again moves it. Given with since, the answer counts from the mark before it moves, which times one span after another. Default false.`
|
|
36198
|
+
},
|
|
36199
|
+
since: {
|
|
36200
|
+
type: "string",
|
|
36201
|
+
enum: ["mark"],
|
|
36202
|
+
ops: ["profile"],
|
|
36203
|
+
description: 'profile: "mark" answers only what was added after the mark that mark: true set in this run: per function the calls and self and total milliseconds since it, with the functions that added no call left out, frames and coveredMs since it, and callersOf read off the same difference. Refused when no mark was set in this run, or when the profile holds less than it did at the mark, which is a profiler started again since.'
|
|
36204
|
+
},
|
|
35934
36205
|
limit: {
|
|
35935
36206
|
type: "integer",
|
|
35936
36207
|
minimum: 1,
|
|
@@ -35982,7 +36253,7 @@ var TOOL_SPECS = [
|
|
|
35982
36253
|
requires: []
|
|
35983
36254
|
}
|
|
35984
36255
|
},
|
|
35985
|
-
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
|
|
36256
|
+
answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
|
|
35986
36257
|
defaultOperation: "start"
|
|
35987
36258
|
},
|
|
35988
36259
|
{
|
|
@@ -36078,9 +36349,10 @@ var TOOL_SPECS = [
|
|
|
36078
36349
|
description: "The engine's ClassDB: find classes, read one in full, or walk an inheritance tree.",
|
|
36079
36350
|
parameters: {
|
|
36080
36351
|
projectPath: PROJECT_PATH2,
|
|
36081
|
-
filter: { type: "string", description: "query: a substring of the class name." },
|
|
36352
|
+
filter: { type: "string", ops: ["query"], description: "query: a substring of the class name." },
|
|
36082
36353
|
category: {
|
|
36083
36354
|
type: "string",
|
|
36355
|
+
ops: ["query"],
|
|
36084
36356
|
enum: [
|
|
36085
36357
|
"node",
|
|
36086
36358
|
"node2d",
|
|
@@ -36097,9 +36369,21 @@ var TOOL_SPECS = [
|
|
|
36097
36369
|
],
|
|
36098
36370
|
description: "query: limit to one family."
|
|
36099
36371
|
},
|
|
36100
|
-
instantiableOnly: {
|
|
36101
|
-
|
|
36102
|
-
|
|
36372
|
+
instantiableOnly: {
|
|
36373
|
+
type: "boolean",
|
|
36374
|
+
ops: ["query"],
|
|
36375
|
+
description: "query: leave out abstract classes. Default false."
|
|
36376
|
+
},
|
|
36377
|
+
className: {
|
|
36378
|
+
type: "string",
|
|
36379
|
+
ops: ["info", "inheritance"],
|
|
36380
|
+
description: "info, inheritance: the class."
|
|
36381
|
+
},
|
|
36382
|
+
includeInherited: {
|
|
36383
|
+
type: "boolean",
|
|
36384
|
+
ops: ["info"],
|
|
36385
|
+
description: "info: include inherited members. Default false."
|
|
36386
|
+
},
|
|
36103
36387
|
member: {
|
|
36104
36388
|
type: "string",
|
|
36105
36389
|
ops: ["info"],
|
|
@@ -36253,7 +36537,7 @@ var TOOL_SPECS = [
|
|
|
36253
36537
|
value: {
|
|
36254
36538
|
ops: ["set"],
|
|
36255
36539
|
blank: true,
|
|
36256
|
-
description: `set: the value, fitted to the property's type. A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" 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 fraction into one holding an int included. 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.`
|
|
36540
|
+
description: `set: the value, fitted to the property's type. ${WHOLE_NUMBERS} A number the property would have to cut is refused rather than truncated: a fraction into an int, a number other than 0 or 1 into a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}, which is the form a read answers with. "" 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 fraction into one holding an int included. 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.`
|
|
36257
36541
|
},
|
|
36258
36542
|
method: {
|
|
36259
36543
|
type: "string",
|
|
@@ -36263,7 +36547,7 @@ var TOOL_SPECS = [
|
|
|
36263
36547
|
args: {
|
|
36264
36548
|
type: "array",
|
|
36265
36549
|
ops: ["call"],
|
|
36266
|
-
description: `call: the arguments, fitted to the method's parameter types. A number a parameter would have to cut is refused rather than truncated: a fraction for an int, a number other than 0 or 1 for a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. 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. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
|
|
36550
|
+
description: `call: the arguments, fitted to the method's parameter types. An RID is passed as the {"_type": "RID", "id": ...} a read answers it with, such as a viewport's for RenderingServer's render timing. ${WHOLE_NUMBERS} A number a parameter would have to cut is refused rather than truncated: a fraction for an int, a number other than 0 or 1 for a bool. A vector, colour or the like may be written as its fields, {"x": 140, "y": 300}, or tagged, {"_type": "Vector2", "x": 140, "y": 300}. 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. The method itself takes its arguments here only: brackets on it holding arguments are refused.`
|
|
36267
36551
|
},
|
|
36268
36552
|
properties: {
|
|
36269
36553
|
type: ["boolean", "array"],
|
|
@@ -36767,16 +37051,16 @@ function serverInstructions() {
|
|
|
36767
37051
|
import { createHash as createHash5 } from "node:crypto";
|
|
36768
37052
|
import {
|
|
36769
37053
|
cpSync,
|
|
36770
|
-
existsSync as
|
|
37054
|
+
existsSync as existsSync20,
|
|
36771
37055
|
mkdirSync as mkdirSync14,
|
|
36772
|
-
readdirSync as
|
|
37056
|
+
readdirSync as readdirSync11,
|
|
36773
37057
|
readFileSync as readFileSync23,
|
|
36774
37058
|
rmdirSync,
|
|
36775
37059
|
rmSync as rmSync12,
|
|
36776
|
-
statSync as
|
|
37060
|
+
statSync as statSync12,
|
|
36777
37061
|
writeFileSync as writeFileSync15
|
|
36778
37062
|
} from "node:fs";
|
|
36779
|
-
import { dirname as
|
|
37063
|
+
import { dirname as dirname18, join as join26 } from "node:path";
|
|
36780
37064
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
36781
37065
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
36782
37066
|
var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
|
|
@@ -36795,13 +37079,13 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
36795
37079
|
function addonsDigest(names, from) {
|
|
36796
37080
|
const hash = createHash5("sha256");
|
|
36797
37081
|
const walk = (directory, relative) => {
|
|
36798
|
-
for (const name of
|
|
37082
|
+
for (const name of readdirSync11(directory).sort()) {
|
|
36799
37083
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
36800
37084
|
continue;
|
|
36801
37085
|
}
|
|
36802
|
-
const path =
|
|
37086
|
+
const path = join26(directory, name);
|
|
36803
37087
|
const inside = `${relative}/${name}`;
|
|
36804
|
-
if (
|
|
37088
|
+
if (statSync12(path).isDirectory()) {
|
|
36805
37089
|
walk(path, inside);
|
|
36806
37090
|
} else {
|
|
36807
37091
|
hash.update(`${inside}
|
|
@@ -36813,7 +37097,7 @@ function addonsDigest(names, from) {
|
|
|
36813
37097
|
}
|
|
36814
37098
|
};
|
|
36815
37099
|
for (const name of names) {
|
|
36816
|
-
walk(
|
|
37100
|
+
walk(join26(from, name), name);
|
|
36817
37101
|
}
|
|
36818
37102
|
return hash.digest("hex");
|
|
36819
37103
|
}
|
|
@@ -36846,19 +37130,19 @@ function installedRuntimeDigest(projectPath) {
|
|
|
36846
37130
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
36847
37131
|
}
|
|
36848
37132
|
function digestMarker(projectPath, addon) {
|
|
36849
|
-
const marker =
|
|
36850
|
-
if (!
|
|
37133
|
+
const marker = join26(projectPath, "addons", addon, DIGEST_MARKER);
|
|
37134
|
+
if (!existsSync20(marker)) {
|
|
36851
37135
|
return null;
|
|
36852
37136
|
}
|
|
36853
37137
|
const said = readFileSync23(marker, "utf8").trim();
|
|
36854
37138
|
return said === "" ? null : said;
|
|
36855
37139
|
}
|
|
36856
37140
|
function shippedAddonsDirectory() {
|
|
36857
|
-
return
|
|
37141
|
+
return join26(dirname18(fileURLToPath3(import.meta.url)), "godot", "addons");
|
|
36858
37142
|
}
|
|
36859
37143
|
function installedAddonVersion(projectPath) {
|
|
36860
|
-
const marker =
|
|
36861
|
-
if (!
|
|
37144
|
+
const marker = join26(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
37145
|
+
if (!existsSync20(marker)) {
|
|
36862
37146
|
return null;
|
|
36863
37147
|
}
|
|
36864
37148
|
const said = readFileSync23(marker, "utf8").trim();
|
|
@@ -36872,7 +37156,7 @@ function loaderAmong(projectPath, named) {
|
|
|
36872
37156
|
}
|
|
36873
37157
|
let source = null;
|
|
36874
37158
|
try {
|
|
36875
|
-
source = readFileSync23(
|
|
37159
|
+
source = readFileSync23(join26(projectPath, path), "utf8");
|
|
36876
37160
|
} catch {}
|
|
36877
37161
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
36878
37162
|
return { name: entry, path, exists: true };
|
|
@@ -36914,10 +37198,10 @@ function enabledPlugins(settings) {
|
|
|
36914
37198
|
function inspectProject(projectPath) {
|
|
36915
37199
|
const problems = [];
|
|
36916
37200
|
const addons = ADDONS.map((name) => {
|
|
36917
|
-
const directory =
|
|
36918
|
-
const marker =
|
|
36919
|
-
const installed =
|
|
36920
|
-
const version =
|
|
37201
|
+
const directory = join26(projectPath, "addons", name);
|
|
37202
|
+
const marker = join26(directory, VERSION_MARKER);
|
|
37203
|
+
const installed = existsSync20(directory);
|
|
37204
|
+
const version = existsSync20(marker) ? readFileSync23(marker, "utf8").trim() : null;
|
|
36921
37205
|
const current = version === SERVER_VERSION;
|
|
36922
37206
|
if (!installed) {
|
|
36923
37207
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -36926,7 +37210,7 @@ function inspectProject(projectPath) {
|
|
|
36926
37210
|
}
|
|
36927
37211
|
return { name, installed, version, current };
|
|
36928
37212
|
});
|
|
36929
|
-
const settings = parseProjectGodot(readFileSync23(
|
|
37213
|
+
const settings = parseProjectGodot(readFileSync23(join26(projectPath, "project.godot"), "utf8"));
|
|
36930
37214
|
const pluginsEnabled = enabledPlugins(settings);
|
|
36931
37215
|
for (const name of EDITOR_PLUGINS) {
|
|
36932
37216
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -36942,7 +37226,7 @@ function inspectProject(projectPath) {
|
|
|
36942
37226
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
36943
37227
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
36944
37228
|
for (const [name, path] of named) {
|
|
36945
|
-
if (!
|
|
37229
|
+
if (!existsSync20(join26(projectPath, path))) {
|
|
36946
37230
|
if (!addonMissing(path)) {
|
|
36947
37231
|
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`);
|
|
36948
37232
|
}
|
|
@@ -36992,7 +37276,9 @@ function argumentShape(schema) {
|
|
|
36992
37276
|
// src/server.ts
|
|
36993
37277
|
var UPDATE_NOTICE_EVERY = 500;
|
|
36994
37278
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
36995
|
-
var
|
|
37279
|
+
var FAILED_BUDGET = 1e6;
|
|
37280
|
+
var ANSWER_BUDGET = 4000000;
|
|
37281
|
+
var __dirname2 = dirname19(fileURLToPath4(import.meta.url));
|
|
36996
37282
|
var PARENT_ASKED_AFTER_MS = 5000;
|
|
36997
37283
|
function editorRestartTimeoutMs() {
|
|
36998
37284
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
@@ -37024,22 +37310,22 @@ function writtenFileRefusal(path, rule) {
|
|
|
37024
37310
|
if (/^(res|user):\/\//.test(path)) {
|
|
37025
37311
|
return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
|
|
37026
37312
|
}
|
|
37027
|
-
if (!
|
|
37313
|
+
if (!isAbsolute5(path)) {
|
|
37028
37314
|
return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
|
|
37029
37315
|
}
|
|
37030
37316
|
if (!rule.extensions.includes(extname4(path).toLowerCase())) {
|
|
37031
37317
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
37032
37318
|
}
|
|
37033
|
-
const directory = realPathOr(
|
|
37034
|
-
if (!
|
|
37035
|
-
return `${named}'s directory ${
|
|
37319
|
+
const directory = realPathOr(dirname19(path));
|
|
37320
|
+
if (!existsSync21(directory)) {
|
|
37321
|
+
return `${named}'s directory ${dirname19(path)} does not exist; ${what} creates no directories.`;
|
|
37036
37322
|
}
|
|
37037
|
-
const where =
|
|
37323
|
+
const where = join27(directory, basename8(path));
|
|
37038
37324
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
37039
37325
|
if (inside !== undefined) {
|
|
37040
37326
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
37041
37327
|
}
|
|
37042
|
-
const taken = rule.writes(where).find((file) =>
|
|
37328
|
+
const taken = rule.writes(where).find((file) => existsSync21(file));
|
|
37043
37329
|
if (taken !== undefined) {
|
|
37044
37330
|
return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
|
|
37045
37331
|
}
|
|
@@ -37150,7 +37436,7 @@ function exited(run) {
|
|
|
37150
37436
|
return run.exitCode !== null || run.exitSignal !== null;
|
|
37151
37437
|
}
|
|
37152
37438
|
function howItExited(run) {
|
|
37153
|
-
return run.exitSignal === null ?
|
|
37439
|
+
return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}, with no exit code`;
|
|
37154
37440
|
}
|
|
37155
37441
|
function errorsUnread(run) {
|
|
37156
37442
|
return run.throughEditor && typeof run.errorReport !== "string";
|
|
@@ -37263,7 +37549,7 @@ function endedWithoutACode(run) {
|
|
|
37263
37549
|
function ourRunEnded(run, now) {
|
|
37264
37550
|
const which = `${run.throughEditor ? "played in the editor" : "started"}${run.pid === null ? "" : ` (pid ${run.pid})`}`;
|
|
37265
37551
|
const when = run.endedAt === undefined ? "" : ` ${Math.max(0, Math.round((now - run.endedAt) / 1000))}s ago`;
|
|
37266
|
-
const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with
|
|
37552
|
+
const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with ${exitCodeText(run.exitCode)}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
|
|
37267
37553
|
return `No game with the runtime addon is running: the game this server ${which} ended${when}, and ${how}. editor_output has what it printed, and editor_run start runs it again.`;
|
|
37268
37554
|
}
|
|
37269
37555
|
function stopVerdict(stop) {
|
|
@@ -37601,7 +37887,7 @@ function notRunNote(notRun, failFast) {
|
|
|
37601
37887
|
function exportPresetRefusal(projectPath, preset) {
|
|
37602
37888
|
let text;
|
|
37603
37889
|
try {
|
|
37604
|
-
text = readFileSync24(
|
|
37890
|
+
text = readFileSync24(join27(projectPath, "export_presets.cfg"), "utf8");
|
|
37605
37891
|
} catch {
|
|
37606
37892
|
return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. project_export add_preset writes one, as the editor's Project > Export does.`;
|
|
37607
37893
|
}
|
|
@@ -37623,7 +37909,7 @@ function exportPresetRefusal(projectPath, preset) {
|
|
|
37623
37909
|
}
|
|
37624
37910
|
return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. project_export add_preset adds one.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
|
|
37625
37911
|
}
|
|
37626
|
-
function exportAnswer(ending, asked, before, after, beside = []) {
|
|
37912
|
+
function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null) {
|
|
37627
37913
|
const { log } = ending;
|
|
37628
37914
|
const written = after !== null && after !== before;
|
|
37629
37915
|
const notCopied = librariesNotCopied(log.all);
|
|
@@ -37640,6 +37926,7 @@ function exportAnswer(ending, asked, before, after, beside = []) {
|
|
|
37640
37926
|
librariesNotLoaded: notCopied,
|
|
37641
37927
|
extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
|
|
37642
37928
|
},
|
|
37929
|
+
...docCacheNote === null ? {} : { docCacheNote },
|
|
37643
37930
|
...foldedEntriesAnswer(log)
|
|
37644
37931
|
};
|
|
37645
37932
|
if (verdict.exported) {
|
|
@@ -37664,9 +37951,9 @@ function writtenBeside(before, after, output, within) {
|
|
|
37664
37951
|
function filesIn(folder) {
|
|
37665
37952
|
const found = new Map;
|
|
37666
37953
|
try {
|
|
37667
|
-
for (const entry of
|
|
37954
|
+
for (const entry of readdirSync12(folder, { withFileTypes: true })) {
|
|
37668
37955
|
if (entry.isFile()) {
|
|
37669
|
-
found.set(entry.name,
|
|
37956
|
+
found.set(entry.name, statSync13(join27(folder, entry.name)).mtimeMs);
|
|
37670
37957
|
}
|
|
37671
37958
|
}
|
|
37672
37959
|
} catch {}
|
|
@@ -37700,7 +37987,7 @@ function unsavedInEditorNote(scripts) {
|
|
|
37700
37987
|
return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
|
|
37701
37988
|
}
|
|
37702
37989
|
function editorAddonMissing(projectPath) {
|
|
37703
|
-
if (!
|
|
37990
|
+
if (!existsSync21(join27(projectPath, "project.godot"))) {
|
|
37704
37991
|
return null;
|
|
37705
37992
|
}
|
|
37706
37993
|
const report = inspectProject(projectPath);
|
|
@@ -37988,7 +38275,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
37988
38275
|
let projectGodot = null;
|
|
37989
38276
|
if (projectPath !== null) {
|
|
37990
38277
|
try {
|
|
37991
|
-
projectGodot = readFileSync24(
|
|
38278
|
+
projectGodot = readFileSync24(join27(projectPath, "project.godot"), "utf8");
|
|
37992
38279
|
} catch {}
|
|
37993
38280
|
}
|
|
37994
38281
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -38166,7 +38453,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
38166
38453
|
var ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd";
|
|
38167
38454
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
38168
38455
|
try {
|
|
38169
|
-
return settingKeys(readFileSync24(
|
|
38456
|
+
return settingKeys(readFileSync24(join27(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
38170
38457
|
} catch {
|
|
38171
38458
|
return true;
|
|
38172
38459
|
}
|
|
@@ -38181,13 +38468,13 @@ function uncaughtNote(uncaught) {
|
|
|
38181
38468
|
}
|
|
38182
38469
|
function elsewhereIn(projectPath, asked) {
|
|
38183
38470
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
38184
|
-
if (wanted === "" ||
|
|
38471
|
+
if (wanted === "" || existsSync21(join27(projectPath, wanted))) {
|
|
38185
38472
|
return "";
|
|
38186
38473
|
}
|
|
38187
38474
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
38188
38475
|
let near = [];
|
|
38189
38476
|
try {
|
|
38190
|
-
near =
|
|
38477
|
+
near = readdirSync12(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
|
|
38191
38478
|
} catch {
|
|
38192
38479
|
return "";
|
|
38193
38480
|
}
|
|
@@ -38279,23 +38566,23 @@ function engineChecked(why) {
|
|
|
38279
38566
|
var EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
38280
38567
|
function isDirectory(path) {
|
|
38281
38568
|
try {
|
|
38282
|
-
return
|
|
38569
|
+
return statSync13(path).isDirectory();
|
|
38283
38570
|
} catch {
|
|
38284
38571
|
return false;
|
|
38285
38572
|
}
|
|
38286
38573
|
}
|
|
38287
38574
|
function scriptsUnder(directory, prefix) {
|
|
38288
|
-
if (
|
|
38575
|
+
if (existsSync21(join27(directory, ".gdignore"))) {
|
|
38289
38576
|
return [];
|
|
38290
38577
|
}
|
|
38291
38578
|
const found = [];
|
|
38292
|
-
for (const entry of
|
|
38579
|
+
for (const entry of readdirSync12(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
38293
38580
|
if (entry.name.startsWith(".")) {
|
|
38294
38581
|
continue;
|
|
38295
38582
|
}
|
|
38296
38583
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
38297
38584
|
if (entry.isDirectory()) {
|
|
38298
|
-
found.push(...scriptsUnder(
|
|
38585
|
+
found.push(...scriptsUnder(join27(directory, entry.name), path));
|
|
38299
38586
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
38300
38587
|
found.push(path);
|
|
38301
38588
|
}
|
|
@@ -38311,7 +38598,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
38311
38598
|
return false;
|
|
38312
38599
|
}
|
|
38313
38600
|
try {
|
|
38314
|
-
return readFileSync24(
|
|
38601
|
+
return readFileSync24(join27(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
38315
38602
|
} catch {
|
|
38316
38603
|
return false;
|
|
38317
38604
|
}
|
|
@@ -38337,7 +38624,7 @@ var STATIC_LIST_HINT = { ttlMs: 5 * 60000, cacheScope: "private" };
|
|
|
38337
38624
|
class GodotServer {
|
|
38338
38625
|
stdio = null;
|
|
38339
38626
|
locator = new GodotLocator;
|
|
38340
|
-
operationsScript =
|
|
38627
|
+
operationsScript = join27(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
38341
38628
|
godotBridge;
|
|
38342
38629
|
tools = buildToolDefinitions();
|
|
38343
38630
|
activeProcess = null;
|
|
@@ -38364,6 +38651,7 @@ class GodotServer {
|
|
|
38364
38651
|
inFlight = new Map;
|
|
38365
38652
|
callsOnGames = new Set;
|
|
38366
38653
|
callsTaken = 0;
|
|
38654
|
+
answersDirectory;
|
|
38367
38655
|
clientNoted = null;
|
|
38368
38656
|
parentCommand = null;
|
|
38369
38657
|
endNoted = false;
|
|
@@ -38597,14 +38885,14 @@ class GodotServer {
|
|
|
38597
38885
|
}
|
|
38598
38886
|
noteTheStart() {
|
|
38599
38887
|
const cwd = process.cwd();
|
|
38600
|
-
const mcpJson =
|
|
38888
|
+
const mcpJson = join27(cwd, ".mcp.json");
|
|
38601
38889
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
38602
38890
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
38603
38891
|
writeLifecycle("started", {
|
|
38604
38892
|
argv: process.argv.slice(1),
|
|
38605
38893
|
cwd,
|
|
38606
38894
|
project: this.ownProject,
|
|
38607
|
-
mcpJson:
|
|
38895
|
+
mcpJson: existsSync21(mcpJson) ? mcpJson : null
|
|
38608
38896
|
});
|
|
38609
38897
|
if (predecessor !== null) {
|
|
38610
38898
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -38745,7 +39033,7 @@ class GodotServer {
|
|
|
38745
39033
|
return checked.response;
|
|
38746
39034
|
}
|
|
38747
39035
|
const named = args["projectPath"];
|
|
38748
|
-
if (typeof named === "string" &&
|
|
39036
|
+
if (typeof named === "string" && existsSync21(join27(named, "project.godot"))) {
|
|
38749
39037
|
this.lastProjectPath = named;
|
|
38750
39038
|
}
|
|
38751
39039
|
this.updates.refresh();
|
|
@@ -38762,12 +39050,66 @@ class GodotServer {
|
|
|
38762
39050
|
this.inFlight.set(call, { tool: spec.name, op: checked.op ?? "", startedAt: Date.now() });
|
|
38763
39051
|
try {
|
|
38764
39052
|
const answer = await withCallSignal(ctx.mcpReq.signal, () => this.answered(spec.name, checked.op ?? "", args), progress);
|
|
38765
|
-
return this.withFeedbackNotice(this.withUpdateNotice(answer));
|
|
39053
|
+
return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(answer)));
|
|
38766
39054
|
} finally {
|
|
38767
39055
|
this.inFlight.delete(call);
|
|
38768
39056
|
}
|
|
38769
39057
|
});
|
|
38770
39058
|
}
|
|
39059
|
+
withinTheClientsLimit(tool, answer) {
|
|
39060
|
+
const sizeOf = (item) => item.type === "text" ? Buffer.byteLength(item.text) : item.data.length;
|
|
39061
|
+
const bytes = answer.content.reduce((sum, item) => sum + sizeOf(item), 0);
|
|
39062
|
+
if (bytes <= ANSWER_BUDGET) {
|
|
39063
|
+
return answer;
|
|
39064
|
+
}
|
|
39065
|
+
this.answersDirectory ??= scratchDirectory("answers");
|
|
39066
|
+
const directory = this.answersDirectory;
|
|
39067
|
+
const stem = `${tool}-${Date.now()}`;
|
|
39068
|
+
let savedAt;
|
|
39069
|
+
let notSaved;
|
|
39070
|
+
try {
|
|
39071
|
+
savedAt = answer.content.map((item, index) => {
|
|
39072
|
+
const file = join27(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
|
|
39073
|
+
writeFileSync16(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
|
|
39074
|
+
return file;
|
|
39075
|
+
});
|
|
39076
|
+
} catch (error) {
|
|
39077
|
+
notSaved = errorMessage(error);
|
|
39078
|
+
}
|
|
39079
|
+
const first = answer.content[0];
|
|
39080
|
+
let parsed = null;
|
|
39081
|
+
try {
|
|
39082
|
+
parsed = first?.type === "text" ? JSON.parse(first.text) : null;
|
|
39083
|
+
} catch {}
|
|
39084
|
+
const small = {};
|
|
39085
|
+
const leftOut = [];
|
|
39086
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
39087
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
39088
|
+
if (JSON.stringify(value).length <= 2000) {
|
|
39089
|
+
small[key] = value;
|
|
39090
|
+
} else {
|
|
39091
|
+
leftOut.push(key);
|
|
39092
|
+
}
|
|
39093
|
+
}
|
|
39094
|
+
}
|
|
39095
|
+
return {
|
|
39096
|
+
...answer,
|
|
39097
|
+
content: [
|
|
39098
|
+
{
|
|
39099
|
+
type: "text",
|
|
39100
|
+
text: answerJson({
|
|
39101
|
+
...small,
|
|
39102
|
+
answerTooLarge: {
|
|
39103
|
+
bytes,
|
|
39104
|
+
savedAt,
|
|
39105
|
+
leftOut: leftOut.length > 0 ? leftOut : undefined,
|
|
39106
|
+
note: savedAt === undefined ? `The answer was ${bytes} bytes, past the ${ANSWER_BUDGET} this server sends in one message (Claude Code closes the connection past 16 MB), and writing it to a file failed: ${notSaved ?? "no reason given"}. The fields of its top level that are short are kept here.` : `The answer was ${bytes} bytes, past the ${ANSWER_BUDGET} this server sends in one message (Claude Code closes the connection past 16 MB), so it was written whole to savedAt; read it from there. The fields of its top level that are short are kept here. The files are removed once this server has stopped.`
|
|
39107
|
+
}
|
|
39108
|
+
})
|
|
39109
|
+
}
|
|
39110
|
+
]
|
|
39111
|
+
};
|
|
39112
|
+
}
|
|
38771
39113
|
async answered(tool, op, args) {
|
|
38772
39114
|
try {
|
|
38773
39115
|
return await this.dispatch(tool, op, args);
|
|
@@ -39019,6 +39361,7 @@ class GodotServer {
|
|
|
39019
39361
|
const contained = this.containProjectFiles({ ...arguments_, ...extra });
|
|
39020
39362
|
return contained.ok ? await this.handleViaBridge(command, contained.value, op === "" ? tool : `${tool} ${op}`) : contained.response;
|
|
39021
39363
|
};
|
|
39364
|
+
const editorOrEngine = async (command, effect, extra = {}) => await this.editorOrEngine(command, { ...arguments_, ...extra }, `${tool} ${op}`, effect);
|
|
39022
39365
|
switch (tool) {
|
|
39023
39366
|
case "project_info":
|
|
39024
39367
|
return await this.handleProjectInfo(args);
|
|
@@ -39031,7 +39374,7 @@ class GodotServer {
|
|
|
39031
39374
|
case "project_test":
|
|
39032
39375
|
return await this.handleRunTests(args);
|
|
39033
39376
|
case "scene_create":
|
|
39034
|
-
return op === "create" ? await
|
|
39377
|
+
return op === "create" ? await editorOrEngine("create_scene", "writes") : await editorOrEngine("save_scene", "writes");
|
|
39035
39378
|
case "scene_tree":
|
|
39036
39379
|
return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
|
|
39037
39380
|
scenePath: file,
|
|
@@ -39041,28 +39384,28 @@ class GodotServer {
|
|
|
39041
39384
|
case "scene_node":
|
|
39042
39385
|
switch (op) {
|
|
39043
39386
|
case "add":
|
|
39044
|
-
return await
|
|
39387
|
+
return await editorOrEngine("add_node", "writes");
|
|
39045
39388
|
case "get":
|
|
39046
|
-
return await
|
|
39389
|
+
return await editorOrEngine("get_node_properties", "reads");
|
|
39047
39390
|
case "set":
|
|
39048
|
-
return await
|
|
39391
|
+
return await editorOrEngine("set_node_properties", "writes");
|
|
39049
39392
|
case "duplicate":
|
|
39050
|
-
return await
|
|
39393
|
+
return await editorOrEngine("duplicate_node", "writes", { parentPath: args["parentNodePath"] });
|
|
39051
39394
|
case "reparent":
|
|
39052
|
-
return await
|
|
39395
|
+
return await editorOrEngine("reparent_node", "writes");
|
|
39053
39396
|
case "delete":
|
|
39054
|
-
return await
|
|
39397
|
+
return await editorOrEngine("delete_node", "writes");
|
|
39055
39398
|
default:
|
|
39056
|
-
return await
|
|
39399
|
+
return await editorOrEngine("set_tilemap_cells", "writes", { tilemapNodePath: args["nodePath"] });
|
|
39057
39400
|
}
|
|
39058
39401
|
case "scene_signal":
|
|
39059
39402
|
switch (op) {
|
|
39060
39403
|
case "connect":
|
|
39061
|
-
return await
|
|
39404
|
+
return await editorOrEngine("connect_signal", "writes");
|
|
39062
39405
|
case "disconnect":
|
|
39063
|
-
return await
|
|
39406
|
+
return await editorOrEngine("disconnect_signal", "writes");
|
|
39064
39407
|
default:
|
|
39065
|
-
return await
|
|
39408
|
+
return await editorOrEngine("list_connections", "reads");
|
|
39066
39409
|
}
|
|
39067
39410
|
case "scene_animation":
|
|
39068
39411
|
switch (op) {
|
|
@@ -39113,17 +39456,17 @@ class GodotServer {
|
|
|
39113
39456
|
}), async () => await bridge("read_resource", kept));
|
|
39114
39457
|
}
|
|
39115
39458
|
case "create":
|
|
39116
|
-
return await
|
|
39459
|
+
return await editorOrEngine("create_resource", "writes");
|
|
39117
39460
|
case "modify":
|
|
39118
|
-
return await
|
|
39461
|
+
return await editorOrEngine("modify_resource", "writes");
|
|
39119
39462
|
case "create_shader":
|
|
39120
|
-
return await
|
|
39463
|
+
return await editorOrEngine("create_shader", "writes", { shaderPath: args["resourcePath"] });
|
|
39121
39464
|
case "create_tileset":
|
|
39122
|
-
return await
|
|
39465
|
+
return await editorOrEngine("create_tileset", "writes", { tilesetPath: args["resourcePath"] });
|
|
39123
39466
|
case "set_theme_color":
|
|
39124
|
-
return await
|
|
39467
|
+
return await editorOrEngine("set_theme_color", "writes", { themePath: args["resourcePath"] });
|
|
39125
39468
|
default:
|
|
39126
|
-
return await
|
|
39469
|
+
return await editorOrEngine("set_theme_font_size", "writes", { themePath: args["resourcePath"] });
|
|
39127
39470
|
}
|
|
39128
39471
|
case "editor_launch":
|
|
39129
39472
|
if (op === "close") {
|
|
@@ -39344,8 +39687,8 @@ class GodotServer {
|
|
|
39344
39687
|
if (path === undefined) {
|
|
39345
39688
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
39346
39689
|
}
|
|
39347
|
-
const file =
|
|
39348
|
-
if (!
|
|
39690
|
+
const file = join27(path, "project.godot");
|
|
39691
|
+
if (!existsSync21(file)) {
|
|
39349
39692
|
return {
|
|
39350
39693
|
ok: false,
|
|
39351
39694
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -39391,7 +39734,7 @@ class GodotServer {
|
|
|
39391
39734
|
}
|
|
39392
39735
|
runEnvironment(args) {
|
|
39393
39736
|
const savesIn = readNonEmptyString(args, "savesIn");
|
|
39394
|
-
if (savesIn !== undefined && !
|
|
39737
|
+
if (savesIn !== undefined && !isAbsolute5(savesIn)) {
|
|
39395
39738
|
return {
|
|
39396
39739
|
ok: false,
|
|
39397
39740
|
response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
|
|
@@ -39650,7 +39993,8 @@ class GodotServer {
|
|
|
39650
39993
|
uidsCreated: given,
|
|
39651
39994
|
stillWithoutUid: after,
|
|
39652
39995
|
note: uidsLeftNote(after.length),
|
|
39653
|
-
...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
|
|
39996
|
+
...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
|
|
39997
|
+
...imported.docCacheNote === undefined ? {} : { docCacheNote: imported.docCacheNote }
|
|
39654
39998
|
}
|
|
39655
39999
|
}, scanned));
|
|
39656
40000
|
}
|
|
@@ -39889,6 +40233,9 @@ class GodotServer {
|
|
|
39889
40233
|
if (imported.extensionNote !== undefined) {
|
|
39890
40234
|
extra["extensionNote"] = imported.extensionNote;
|
|
39891
40235
|
}
|
|
40236
|
+
if (imported.docCacheNote !== undefined) {
|
|
40237
|
+
extra["docCacheNote"] = imported.docCacheNote;
|
|
40238
|
+
}
|
|
39892
40239
|
}
|
|
39893
40240
|
const after = await this.operation("get_import_status", statusAsk, projectPath);
|
|
39894
40241
|
if (!after.ok) {
|
|
@@ -39989,6 +40336,10 @@ class GodotServer {
|
|
|
39989
40336
|
if (unknown.length > 0) {
|
|
39990
40337
|
return this.createErrorResponse(`project_info cannot include ${unknown.join(", ")}. Sections: ${Object.keys(PROJECT_INFO_SECTIONS2).join(", ")}.`);
|
|
39991
40338
|
}
|
|
40339
|
+
const forValidation = ["preset", "detail"].filter((name) => args[name] !== undefined);
|
|
40340
|
+
if (forValidation.length > 0 && !include.includes("validation")) {
|
|
40341
|
+
return this.createErrorResponse(`${forValidation.join(" and ")} ${forValidation.length === 1 ? "is" : "are"} read only by the validation section, which this call does not include: add "validation" to include, or leave ${forValidation.length === 1 ? "it" : "them"} out.`);
|
|
40342
|
+
}
|
|
39992
40343
|
const project = this.project(args);
|
|
39993
40344
|
if (!project.ok) {
|
|
39994
40345
|
return project.response;
|
|
@@ -40001,7 +40352,7 @@ class GodotServer {
|
|
|
40001
40352
|
const name = application["config/name"];
|
|
40002
40353
|
const mainScene = application["run/main_scene"];
|
|
40003
40354
|
const info = {
|
|
40004
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
40355
|
+
name: typeof name === "string" && name !== "" ? name : basename8(project.value.path),
|
|
40005
40356
|
path: project.value.path,
|
|
40006
40357
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
40007
40358
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -40084,18 +40435,18 @@ class GodotServer {
|
|
|
40084
40435
|
if (!engine.ok) {
|
|
40085
40436
|
return engine.response;
|
|
40086
40437
|
}
|
|
40087
|
-
const folder =
|
|
40438
|
+
const folder = dirname19(output.absolutePath);
|
|
40088
40439
|
try {
|
|
40089
40440
|
mkdirSync15(folder, { recursive: true });
|
|
40090
40441
|
} catch (error) {
|
|
40091
|
-
return this.createErrorResponse(`Cannot create ${
|
|
40442
|
+
return this.createErrorResponse(`Cannot create ${dirname19(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
40092
40443
|
}
|
|
40093
40444
|
const debug = readBoolean(args, "debug") ?? false;
|
|
40094
40445
|
const exportLogs = scratchDirectory("export");
|
|
40095
40446
|
const exportArgs = [
|
|
40096
40447
|
"--headless",
|
|
40097
40448
|
"--log-file",
|
|
40098
|
-
|
|
40449
|
+
join27(exportLogs, "engine.log"),
|
|
40099
40450
|
"--path",
|
|
40100
40451
|
project.value.path,
|
|
40101
40452
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -40103,24 +40454,37 @@ class GodotServer {
|
|
|
40103
40454
|
output.absolutePath
|
|
40104
40455
|
];
|
|
40105
40456
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
40106
|
-
const writtenAt = () =>
|
|
40107
|
-
const
|
|
40108
|
-
const
|
|
40109
|
-
|
|
40110
|
-
|
|
40111
|
-
|
|
40112
|
-
|
|
40113
|
-
|
|
40457
|
+
const writtenAt = () => existsSync21(output.absolutePath) ? statSync13(output.absolutePath).mtimeMs : null;
|
|
40458
|
+
const caches = editorCacheDirectory(engine.value);
|
|
40459
|
+
const exportOnce = async () => {
|
|
40460
|
+
const before = writtenAt();
|
|
40461
|
+
const listedBefore = filesIn(folder);
|
|
40462
|
+
const cachesBefore = docCachesWritten(caches);
|
|
40463
|
+
const freed = freeLibraryCopies(project.value.path);
|
|
40464
|
+
if (freed.setAside.length > 0 || freed.swept.length > 0) {
|
|
40465
|
+
this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
|
|
40466
|
+
}
|
|
40467
|
+
const ending = await runEngine(engine.value, exportArgs, {
|
|
40468
|
+
timeout: { ms: 300000, said: "five minutes" }
|
|
40469
|
+
});
|
|
40470
|
+
const crash = docCacheCrash(ending, cachesBefore, docCachesWritten(caches), "export");
|
|
40471
|
+
return { ending, before, listedBefore, crash };
|
|
40472
|
+
};
|
|
40473
|
+
let run;
|
|
40474
|
+
let docCacheNote;
|
|
40114
40475
|
try {
|
|
40115
|
-
|
|
40476
|
+
const first = await exportOnce();
|
|
40477
|
+
docCacheNote = first.crash;
|
|
40478
|
+
run = first.crash === null ? first : await exportOnce();
|
|
40116
40479
|
} finally {
|
|
40117
40480
|
discard(exportLogs);
|
|
40118
40481
|
}
|
|
40482
|
+
const { ending, before, listedBefore } = run;
|
|
40119
40483
|
if (ending.exitSignal === null && ending.exitCode === null) {
|
|
40120
40484
|
return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
|
|
40121
40485
|
}
|
|
40122
|
-
const beside = writtenBeside(listedBefore, filesIn(folder),
|
|
40123
|
-
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside);
|
|
40486
|
+
const beside = writtenBeside(listedBefore, filesIn(folder), basename8(output.absolutePath), dirname19(output.relativePath).replaceAll("\\", "/"));
|
|
40487
|
+
return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
|
|
40124
40488
|
}
|
|
40125
40489
|
reportTestProgress(log) {
|
|
40126
40490
|
const progress = callProgress();
|
|
@@ -40173,10 +40537,10 @@ class GodotServer {
|
|
|
40173
40537
|
if (movie !== null) {
|
|
40174
40538
|
return this.createErrorResponse(movie);
|
|
40175
40539
|
}
|
|
40176
|
-
const missing = paths.value.filter((path) => !
|
|
40540
|
+
const missing = paths.value.filter((path) => !existsSync21(join27(project.value.path, path.slice("res://".length))));
|
|
40177
40541
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
40178
40542
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
40179
|
-
const noRunner = !
|
|
40543
|
+
const noRunner = !existsSync21(join27(project.value.path, runner));
|
|
40180
40544
|
if (asked.length === 0) {
|
|
40181
40545
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
40182
40546
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -40267,21 +40631,21 @@ class GodotServer {
|
|
|
40267
40631
|
await untilGone([run.process.pid]);
|
|
40268
40632
|
}
|
|
40269
40633
|
await run.settled;
|
|
40270
|
-
const reportsDir =
|
|
40634
|
+
const reportsDir = join27(project.value.path, ".godot", ...ours.split("/"));
|
|
40271
40635
|
let report = null;
|
|
40272
40636
|
let reportProblem = null;
|
|
40273
40637
|
try {
|
|
40274
|
-
const written =
|
|
40638
|
+
const written = existsSync21(reportsDir) ? readdirSync12(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
40275
40639
|
const newest = written.at(-1);
|
|
40276
40640
|
if (newest !== undefined) {
|
|
40277
|
-
report = parseJUnit(readFileSync24(
|
|
40641
|
+
report = parseJUnit(readFileSync24(join27(reportsDir, newest, "results.xml"), "utf8"));
|
|
40278
40642
|
}
|
|
40279
40643
|
} catch (error) {
|
|
40280
40644
|
reportProblem = errorMessage(error);
|
|
40281
40645
|
} finally {
|
|
40282
40646
|
discard(reportsDir, userData);
|
|
40283
40647
|
try {
|
|
40284
|
-
rmdirSync2(
|
|
40648
|
+
rmdirSync2(dirname19(reportsDir));
|
|
40285
40649
|
} catch {}
|
|
40286
40650
|
}
|
|
40287
40651
|
const engineEntries = foldedRepeats(run.log.select({
|
|
@@ -40313,7 +40677,7 @@ class GodotServer {
|
|
|
40313
40677
|
const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
|
|
40314
40678
|
const scriptErrors = [...declaring.origins, ...printedErrors];
|
|
40315
40679
|
const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
|
|
40316
|
-
const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ??
|
|
40680
|
+
const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? howItExited(run);
|
|
40317
40681
|
const nothingAt = missing.join(" and ");
|
|
40318
40682
|
const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
|
|
40319
40683
|
const missingNote = missing.length === 0 ? undefined : `Nothing at ${nothingAt}, so ${missing.length === 1 ? "it was" : "they were"} left out and only ${asked.join(", ")} ran; a run of fewer paths than were asked for is not called a pass.${elsewhereIn(project.value.path, missing[0] ?? "")}`;
|
|
@@ -40373,7 +40737,7 @@ class GodotServer {
|
|
|
40373
40737
|
isError: true
|
|
40374
40738
|
};
|
|
40375
40739
|
}
|
|
40376
|
-
const failed = failedCases(report, Buffer.concat(console2).toString("utf8"));
|
|
40740
|
+
const { failed, briefed } = failedWithin(failedCases(report, Buffer.concat(console2).toString("utf8")), FAILED_BUDGET);
|
|
40377
40741
|
const orphans = orphansPrinted(said);
|
|
40378
40742
|
const leftBehind = new Map(orphans.suites.map((suite) => [suite.path, suite.orphans]));
|
|
40379
40743
|
const orphansIn = (path) => path === null ? 0 : leftBehind.get(path) ?? 0;
|
|
@@ -40413,6 +40777,7 @@ class GodotServer {
|
|
|
40413
40777
|
skipped: report.skipped,
|
|
40414
40778
|
time: report.time,
|
|
40415
40779
|
failed,
|
|
40780
|
+
failedWithoutDetail: briefed > 0 ? briefed : undefined,
|
|
40416
40781
|
hookFailures: hookFailures.length > 0 ? hookFailures : undefined,
|
|
40417
40782
|
uncaughtScriptErrors: uncaughtFolded.length > 0 ? uncaughtFolded : undefined,
|
|
40418
40783
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
@@ -40424,6 +40789,7 @@ class GodotServer {
|
|
|
40424
40789
|
...scanned,
|
|
40425
40790
|
note: [
|
|
40426
40791
|
missingNote,
|
|
40792
|
+
briefed > 0 ? `${briefed} of the ${failed.length} failing cases, those with the largest reports, are named with their first message only, since their reports would have made the answer too large to deliver; pass one case's suite path to see its reports.` : undefined,
|
|
40427
40793
|
notApplied.length > 0 ? "An ignore entry named a case that ran all the same: gdUnit4 leaves a case out only as suite_name:test_name, and reads an entry with no colon as a suite name or a piece of a suite path. ignoreNotApplied gives the form for each case that ran." : undefined,
|
|
40428
40794
|
uncaughtNote(uncaught),
|
|
40429
40795
|
ranAs.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)
|
|
@@ -40587,7 +40953,11 @@ class GodotServer {
|
|
|
40587
40953
|
};
|
|
40588
40954
|
}
|
|
40589
40955
|
markedIfStale(answer) {
|
|
40590
|
-
|
|
40956
|
+
const status = this.godotBridge.getStatus();
|
|
40957
|
+
if (!hasSaidWhoItIs(status)) {
|
|
40958
|
+
return answer;
|
|
40959
|
+
}
|
|
40960
|
+
return markIfStale(answer, status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest(), this.editorCodeOnDisk());
|
|
40591
40961
|
}
|
|
40592
40962
|
async diagnosticsOf(args) {
|
|
40593
40963
|
const waited = await this.waitForTheEditorOpenedHere(args, []);
|
|
@@ -40634,7 +41004,7 @@ class GodotServer {
|
|
|
40634
41004
|
const cached = cachedClasses(projectPath);
|
|
40635
41005
|
const sourceOf = (resourcePath) => {
|
|
40636
41006
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40637
|
-
if (!contained.ok || !
|
|
41007
|
+
if (!contained.ok || !existsSync21(contained.absolutePath)) {
|
|
40638
41008
|
return null;
|
|
40639
41009
|
}
|
|
40640
41010
|
try {
|
|
@@ -40728,7 +41098,7 @@ class GodotServer {
|
|
|
40728
41098
|
}
|
|
40729
41099
|
sourceOfClass(projectPath, resourcePath) {
|
|
40730
41100
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40731
|
-
if (!contained.ok || !
|
|
41101
|
+
if (!contained.ok || !existsSync21(contained.absolutePath)) {
|
|
40732
41102
|
return null;
|
|
40733
41103
|
}
|
|
40734
41104
|
try {
|
|
@@ -40890,7 +41260,7 @@ class GodotServer {
|
|
|
40890
41260
|
...Array.isArray(stack) ? {
|
|
40891
41261
|
stack: stack.map((frame) => ({
|
|
40892
41262
|
...frame,
|
|
40893
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
41263
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join27(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
40894
41264
|
}))
|
|
40895
41265
|
} : {}
|
|
40896
41266
|
});
|
|
@@ -41169,7 +41539,7 @@ class GodotServer {
|
|
|
41169
41539
|
return null;
|
|
41170
41540
|
}
|
|
41171
41541
|
try {
|
|
41172
|
-
return settingKeys(readFileSync24(
|
|
41542
|
+
return settingKeys(readFileSync24(join27(projectPath, "project.godot"), "utf8"));
|
|
41173
41543
|
} catch {
|
|
41174
41544
|
return null;
|
|
41175
41545
|
}
|
|
@@ -41408,7 +41778,7 @@ class GodotServer {
|
|
|
41408
41778
|
return;
|
|
41409
41779
|
}
|
|
41410
41780
|
const connected = this.godotBridge.isConnected() ? this.godotBridge.getStatus().editorPid : undefined;
|
|
41411
|
-
const pids = editorsOfProjectIn(tree, projectPath).filter((pid) => pid !== connected);
|
|
41781
|
+
const pids = editorsOfProjectIn(tree, projectPath).filter((pid) => pid !== connected && alive(pid));
|
|
41412
41782
|
const started = await startTimesOf(pids);
|
|
41413
41783
|
return pids.map((pid) => {
|
|
41414
41784
|
const at = started.get(pid) ?? tree.get(pid)?.startedAt;
|
|
@@ -41606,7 +41976,7 @@ class GodotServer {
|
|
|
41606
41976
|
}
|
|
41607
41977
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
41608
41978
|
const budgetMs = budget.ms;
|
|
41609
|
-
if (!
|
|
41979
|
+
if (!existsSync21(join27(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
41610
41980
|
return runtimeVerdict(null, {
|
|
41611
41981
|
addon: false,
|
|
41612
41982
|
budgetMs,
|
|
@@ -41692,7 +42062,7 @@ class GodotServer {
|
|
|
41692
42062
|
}
|
|
41693
42063
|
let announcedAt;
|
|
41694
42064
|
try {
|
|
41695
|
-
announcedAt =
|
|
42065
|
+
announcedAt = statSync13(endpoint.file).mtimeMs;
|
|
41696
42066
|
} catch {
|
|
41697
42067
|
return;
|
|
41698
42068
|
}
|
|
@@ -42582,9 +42952,13 @@ class GodotServer {
|
|
|
42582
42952
|
if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
|
|
42583
42953
|
return this.createErrorResponse(`callersOf takes a function's name, or Class.name for the functions of one class, and ${JSON.stringify(callersName)} is neither.`, ["editor_run profile names each function as it answers; pass one of those"]);
|
|
42584
42954
|
}
|
|
42955
|
+
const asked = {
|
|
42956
|
+
mark: readBoolean(args, "mark") === true,
|
|
42957
|
+
since: readString(args, "since") === "mark"
|
|
42958
|
+
};
|
|
42585
42959
|
const going = await this.runStillGoing(run);
|
|
42586
42960
|
const projectPath = run.projectPath;
|
|
42587
|
-
const projectGone = projectPath !== null && !
|
|
42961
|
+
const projectGone = projectPath !== null && !existsSync21(join27(projectPath, "project.godot"));
|
|
42588
42962
|
if (projectGone && callersName !== undefined) {
|
|
42589
42963
|
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
42590
42964
|
}
|
|
@@ -42594,7 +42968,7 @@ class GodotServer {
|
|
|
42594
42968
|
...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
|
|
42595
42969
|
};
|
|
42596
42970
|
if (run.throughEditor) {
|
|
42597
|
-
return await this.profileOfThePlayedGame(
|
|
42971
|
+
return await this.profileOfThePlayedGame(run, going, fromProject, limit, asked);
|
|
42598
42972
|
}
|
|
42599
42973
|
const path = run.profile;
|
|
42600
42974
|
if (path === undefined) {
|
|
@@ -42607,15 +42981,16 @@ class GodotServer {
|
|
|
42607
42981
|
]);
|
|
42608
42982
|
}
|
|
42609
42983
|
const totals = file.connected ? file : null;
|
|
42610
|
-
return this.
|
|
42984
|
+
return this.profileReply(run, {
|
|
42611
42985
|
through: "gdharness",
|
|
42612
42986
|
running: going,
|
|
42613
42987
|
totals,
|
|
42614
42988
|
coveredMs: totals?.lastFrameAt == null ? 0 : totals.lastFrameAt - totals.startedAt,
|
|
42615
42989
|
...fromProject
|
|
42616
|
-
}, limit)
|
|
42990
|
+
}, limit, asked);
|
|
42617
42991
|
}
|
|
42618
|
-
async profileOfThePlayedGame(
|
|
42992
|
+
async profileOfThePlayedGame(run, going, fromProject, limit, asked) {
|
|
42993
|
+
const { projectPath } = run;
|
|
42619
42994
|
const read = await this.handleViaBridge("profile_read", {});
|
|
42620
42995
|
if (read.isError === true) {
|
|
42621
42996
|
return read;
|
|
@@ -42637,12 +43012,13 @@ class GodotServer {
|
|
|
42637
43012
|
return this.createErrorResponse("The editor says it is playing and has no debugger session for the game, so there is nothing to switch the profiler on in yet.", ["Ask again in a moment, once the game is up"]);
|
|
42638
43013
|
}
|
|
42639
43014
|
const on = await this.firstProfiledFrame();
|
|
43015
|
+
const unmarked = asked.mark || asked.since ? " Nothing was marked, and nothing is counted from a mark: the profile begins now, so an answer without since covers the same span." : "";
|
|
42640
43016
|
return this.jsonTextResponse({
|
|
42641
43017
|
through: "editor",
|
|
42642
43018
|
running: true,
|
|
42643
43019
|
started: true,
|
|
42644
43020
|
functions: [],
|
|
42645
|
-
note: on ?
|
|
43021
|
+
note: on ? `The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}` : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}`
|
|
42646
43022
|
});
|
|
42647
43023
|
}
|
|
42648
43024
|
const functions = asParams(held["functions"]);
|
|
@@ -42665,13 +43041,70 @@ class GodotServer {
|
|
|
42665
43041
|
];
|
|
42666
43042
|
}))
|
|
42667
43043
|
} : null;
|
|
42668
|
-
return this.
|
|
43044
|
+
return this.profileReply(run, {
|
|
42669
43045
|
through: "editor",
|
|
42670
43046
|
running: going,
|
|
42671
43047
|
totals,
|
|
42672
43048
|
coveredMs: readNonNegativeNumber(held, "coveredMs") ?? 0,
|
|
42673
43049
|
...fromProject
|
|
42674
|
-
}, limit)
|
|
43050
|
+
}, limit, asked);
|
|
43051
|
+
}
|
|
43052
|
+
profileReply(run, reading, limit, asked) {
|
|
43053
|
+
const markPath = profileMarkPath(run.startedAt);
|
|
43054
|
+
const now = Date.now();
|
|
43055
|
+
let shown = reading;
|
|
43056
|
+
const lead = [];
|
|
43057
|
+
let sinceFields = {};
|
|
43058
|
+
if (asked.since) {
|
|
43059
|
+
const mark = readProfileMark(markPath);
|
|
43060
|
+
if (mark === null || mark.runStartedAt !== run.startedAt || mark.projectPath !== run.projectPath) {
|
|
43061
|
+
return this.createErrorResponse("No mark has been set in this run, so there is nothing to count from.", ["editor_run profile with mark: true sets one at the totals it answers with"]);
|
|
43062
|
+
}
|
|
43063
|
+
const added = reading.totals === null ? null : totalsSince(reading.totals, mark.totals);
|
|
43064
|
+
if (added === null) {
|
|
43065
|
+
return this.createErrorResponse("The profile holds less than it did at the mark, so the profiler was started again since, and what it holds now cannot be counted from the mark.", ["editor_run profile with mark: true sets a mark in the profile as it is now"]);
|
|
43066
|
+
}
|
|
43067
|
+
shown = {
|
|
43068
|
+
...reading,
|
|
43069
|
+
totals: added.totals,
|
|
43070
|
+
coveredMs: Math.max(0, reading.coveredMs - mark.coveredMs)
|
|
43071
|
+
};
|
|
43072
|
+
const ago = now - mark.takenAt;
|
|
43073
|
+
sinceFields = {
|
|
43074
|
+
since: "mark",
|
|
43075
|
+
markSetMsAgo: ago,
|
|
43076
|
+
...added.shrunk > 0 ? { lessThanAtMark: added.shrunk } : {}
|
|
43077
|
+
};
|
|
43078
|
+
lead.push(`Counted from the mark set ${ago}ms ago: each function's calls and milliseconds are what it added after the mark, frames and coveredMs are those since it, and a function that added no call is left out.`);
|
|
43079
|
+
if (added.shrunk > 0) {
|
|
43080
|
+
const one = added.shrunk === 1;
|
|
43081
|
+
lead.push(`${one ? "One function reads" : `${added.shrunk} functions read`} less than at the mark and ${one ? "is" : "are"} left out, counted under lessThanAtMark: the engine's own totals arrived after the mark and replaced the frames' sum, and capped totals leave out the functions with the least time.`);
|
|
43082
|
+
}
|
|
43083
|
+
}
|
|
43084
|
+
if (asked.mark) {
|
|
43085
|
+
if (reading.totals === null) {
|
|
43086
|
+
return this.createErrorResponse("There is nothing to mark yet: the game has not connected to the profiler.", ["Ask again in a moment, once the game is up"]);
|
|
43087
|
+
}
|
|
43088
|
+
const mark = {
|
|
43089
|
+
takenAt: now,
|
|
43090
|
+
projectPath: run.projectPath,
|
|
43091
|
+
runStartedAt: run.startedAt,
|
|
43092
|
+
coveredMs: reading.coveredMs,
|
|
43093
|
+
totals: reading.totals
|
|
43094
|
+
};
|
|
43095
|
+
if (!writeProfileMark(markPath, mark)) {
|
|
43096
|
+
return this.createErrorResponse(`The mark could not be written to ${markPath}, so none is set.`);
|
|
43097
|
+
}
|
|
43098
|
+
lead.push(asked.since ? 'The mark is moved to the totals as they are now, so the next since: "mark" counts from here.' : 'The mark is set at these totals: editor_run profile with since: "mark" answers only what is added after it.');
|
|
43099
|
+
}
|
|
43100
|
+
const answer = profileAnswer(shown, limit);
|
|
43101
|
+
const note = [...lead, ...typeof answer["note"] === "string" ? [answer["note"]] : []].join(" ");
|
|
43102
|
+
return this.jsonTextResponse({
|
|
43103
|
+
...answer,
|
|
43104
|
+
...sinceFields,
|
|
43105
|
+
...asked.mark ? { marked: true } : {},
|
|
43106
|
+
note: note === "" ? undefined : note
|
|
43107
|
+
});
|
|
42675
43108
|
}
|
|
42676
43109
|
async firstProfiledFrame() {
|
|
42677
43110
|
const deadline = Date.now() + PROFILER_ON_MS;
|
|
@@ -42872,7 +43305,7 @@ class GodotServer {
|
|
|
42872
43305
|
notes.push(endedWithoutACode({
|
|
42873
43306
|
throughEditor: run.throughEditor,
|
|
42874
43307
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
42875
|
-
couldAnnounce: run.projectPath !== null &&
|
|
43308
|
+
couldAnnounce: run.projectPath !== null && existsSync21(join27(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
42876
43309
|
endedHere: typeof run.endedHere === "string",
|
|
42877
43310
|
pickedUp: run.pickedUp === true
|
|
42878
43311
|
}));
|
|
@@ -43502,7 +43935,7 @@ class GodotServer {
|
|
|
43502
43935
|
if (!scriptPath.ok) {
|
|
43503
43936
|
return this.createErrorResponse(scriptPath.reason);
|
|
43504
43937
|
}
|
|
43505
|
-
if (!
|
|
43938
|
+
if (!existsSync21(scriptPath.absolutePath)) {
|
|
43506
43939
|
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
43507
43940
|
}
|
|
43508
43941
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
@@ -43647,6 +44080,45 @@ class GodotServer {
|
|
|
43647
44080
|
...engineExtras(outcome)
|
|
43648
44081
|
});
|
|
43649
44082
|
}
|
|
44083
|
+
async editorOrEngine(command, args, askedAs, effect) {
|
|
44084
|
+
const contained = this.containProjectFiles(args);
|
|
44085
|
+
if (!contained.ok) {
|
|
44086
|
+
return contained.response;
|
|
44087
|
+
}
|
|
44088
|
+
const project = this.project(contained.value);
|
|
44089
|
+
if (!project.ok) {
|
|
44090
|
+
return project.response;
|
|
44091
|
+
}
|
|
44092
|
+
const projectPath = project.value.path;
|
|
44093
|
+
const waited = await this.waitForTheEditorOpenedHere(contained.value, []);
|
|
44094
|
+
if (this.editorHasOpen(projectPath)) {
|
|
44095
|
+
return await this.handleViaBridge(command, contained.value, askedAs);
|
|
44096
|
+
}
|
|
44097
|
+
if (effect === "writes") {
|
|
44098
|
+
if (waited !== null) {
|
|
44099
|
+
return waited;
|
|
44100
|
+
}
|
|
44101
|
+
const arriving = await this.editorsNotYetConnected(projectPath);
|
|
44102
|
+
if (arriving !== undefined && arriving.length > 0) {
|
|
44103
|
+
const named = arriving.map((one) => `pid ${one.pid}`).join(", ");
|
|
44104
|
+
return this.createErrorResponse(`${askedAs} was not run: an editor of this project is running (${named}) and has not reached this server, and saving a scene or resource it holds would put back what a write made around it.`, [
|
|
44105
|
+
"editor_status names it under editorsNotYetConnected and says when it has connected; the write then goes through it",
|
|
44106
|
+
`If it never connects it is not reaching this server: end ${arriving.length === 1 ? "that pid" : "those pids"} and ask again`
|
|
44107
|
+
]);
|
|
44108
|
+
}
|
|
44109
|
+
}
|
|
44110
|
+
const outcome = await this.operation("run_editor_tool", { command, arguments: contained.value }, projectPath);
|
|
44111
|
+
if (!outcome.ok) {
|
|
44112
|
+
return this.answer(outcome);
|
|
44113
|
+
}
|
|
44114
|
+
return this.jsonTextResponse({
|
|
44115
|
+
...outcome.payload,
|
|
44116
|
+
...effect === "reads" ? {
|
|
44117
|
+
readBy: "a headless engine, since no editor serves this project; unsaved changes in an editor are not seen"
|
|
44118
|
+
} : { writtenBy: "a headless engine, since no editor serves this project" },
|
|
44119
|
+
...engineExtras(outcome)
|
|
44120
|
+
});
|
|
44121
|
+
}
|
|
43650
44122
|
async handleMoveFile(given) {
|
|
43651
44123
|
const project = this.project(given);
|
|
43652
44124
|
if (!project.ok) {
|
|
@@ -44045,7 +44517,7 @@ class GodotServer {
|
|
|
44045
44517
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
44046
44518
|
const run = await this.currentRun();
|
|
44047
44519
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
44048
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
44520
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync21(join27(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
44049
44521
|
return null;
|
|
44050
44522
|
}
|
|
44051
44523
|
const already = runtimesAnnounced().running;
|
|
@@ -44054,7 +44526,7 @@ class GodotServer {
|
|
|
44054
44526
|
return !run.announcedBefore.has(one.pid);
|
|
44055
44527
|
}
|
|
44056
44528
|
try {
|
|
44057
|
-
return
|
|
44529
|
+
return statSync13(one.file).mtimeMs >= run.startedAt;
|
|
44058
44530
|
} catch {
|
|
44059
44531
|
return false;
|
|
44060
44532
|
}
|
|
@@ -44135,7 +44607,7 @@ class GodotServer {
|
|
|
44135
44607
|
}
|
|
44136
44608
|
}
|
|
44137
44609
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
44138
|
-
const screenshotPath = screenshotDir ?
|
|
44610
|
+
const screenshotPath = screenshotDir ? join27(screenshotDir, "capture.png") : null;
|
|
44139
44611
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
44140
44612
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
44141
44613
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|