gdharness 1.2.37 → 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 +607 -374
- package/build/godot/addons/gdharness_editor/headless_check.gd +41 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +86 -23
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +20 -7
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +44 -6
- package/build/godot/operations/godot_operations.gd +10 -3
- package/build/index.js +537 -311
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -3365,18 +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
3377
|
writeFileSync as writeFileSync16
|
|
3378
3378
|
} from "node:fs";
|
|
3379
|
-
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";
|
|
3380
3380
|
import { setTimeout as delay5 } from "node:timers/promises";
|
|
3381
3381
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3382
3382
|
|
|
@@ -28538,19 +28538,11 @@ function dictionary(entries) {
|
|
|
28538
28538
|
return Object.assign(emptyRecord(), entries);
|
|
28539
28539
|
}
|
|
28540
28540
|
|
|
28541
|
-
// src/
|
|
28542
|
-
import {
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
|
|
28546
|
-
openSync as openSync2,
|
|
28547
|
-
readFileSync as readFileSync12,
|
|
28548
|
-
readSync,
|
|
28549
|
-
rmSync as rmSync6,
|
|
28550
|
-
statSync as statSync5,
|
|
28551
|
-
writeFileSync as writeFileSync8
|
|
28552
|
-
} from "node:fs";
|
|
28553
|
-
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";
|
|
28554
28546
|
|
|
28555
28547
|
// src/game-log.ts
|
|
28556
28548
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -28758,6 +28750,9 @@ class GameLog {
|
|
|
28758
28750
|
count(severity) {
|
|
28759
28751
|
return this.entries.filter((entry) => entry.severity === severity).length;
|
|
28760
28752
|
}
|
|
28753
|
+
crashReport() {
|
|
28754
|
+
return this.entries.find((entry) => CRASH.test(entry.text));
|
|
28755
|
+
}
|
|
28761
28756
|
everything() {
|
|
28762
28757
|
return this.entries;
|
|
28763
28758
|
}
|
|
@@ -28791,12 +28786,131 @@ function answersTo(entry, severity, contains) {
|
|
|
28791
28786
|
return [entry.text, ...entry.detail].some((line) => line.toLowerCase().includes(needle));
|
|
28792
28787
|
}
|
|
28793
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
|
+
|
|
28794
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";
|
|
28795
28909
|
function editorLogPath(projectPath) {
|
|
28796
|
-
return
|
|
28910
|
+
return join12(projectPath, ".godot", "gdharness-editor.log");
|
|
28797
28911
|
}
|
|
28798
28912
|
function editorLogNotePath(projectPath) {
|
|
28799
|
-
return
|
|
28913
|
+
return join12(projectPath, ".godot", "gdharness-editor.json");
|
|
28800
28914
|
}
|
|
28801
28915
|
function readEditorLogNote(projectPath) {
|
|
28802
28916
|
try {
|
|
@@ -28811,7 +28925,7 @@ function readEditorLogNote(projectPath) {
|
|
|
28811
28925
|
function clearEditorLog(projectPath) {
|
|
28812
28926
|
try {
|
|
28813
28927
|
const path = editorLogPath(projectPath);
|
|
28814
|
-
mkdirSync7(
|
|
28928
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
28815
28929
|
rmSync6(path, { force: true });
|
|
28816
28930
|
rmSync6(editorLogNotePath(projectPath), { force: true });
|
|
28817
28931
|
} catch {}
|
|
@@ -28819,7 +28933,7 @@ function clearEditorLog(projectPath) {
|
|
|
28819
28933
|
function writeEditorLogNote(projectPath, pid) {
|
|
28820
28934
|
try {
|
|
28821
28935
|
const path = editorLogNotePath(projectPath);
|
|
28822
|
-
mkdirSync7(
|
|
28936
|
+
mkdirSync7(dirname8(path), { recursive: true });
|
|
28823
28937
|
writeFileSync8(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
|
|
28824
28938
|
} catch {}
|
|
28825
28939
|
}
|
|
@@ -28841,7 +28955,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
|
|
|
28841
28955
|
return { kind: "older server", notedPid: noted?.pid ?? null };
|
|
28842
28956
|
}
|
|
28843
28957
|
const path = editorLogPath(projectPath);
|
|
28844
|
-
if (!
|
|
28958
|
+
if (!existsSync8(path)) {
|
|
28845
28959
|
return { kind: "not written" };
|
|
28846
28960
|
}
|
|
28847
28961
|
const log = new GameLog;
|
|
@@ -28900,7 +29014,7 @@ function foldProgress(text) {
|
|
|
28900
29014
|
}
|
|
28901
29015
|
var MOST_CONSOLE_BYTES = 8 * 1024 * 1024;
|
|
28902
29016
|
function theFirstOf(path) {
|
|
28903
|
-
const whole =
|
|
29017
|
+
const whole = statSync6(path).size;
|
|
28904
29018
|
if (whole <= MOST_CONSOLE_BYTES) {
|
|
28905
29019
|
return { text: readFileSync12(path, "utf8") };
|
|
28906
29020
|
}
|
|
@@ -29057,60 +29171,21 @@ function linesBefore(entries, at, collapsed, wanted) {
|
|
|
29057
29171
|
return found.reverse();
|
|
29058
29172
|
}
|
|
29059
29173
|
|
|
29060
|
-
// src/engine-run.ts
|
|
29061
|
-
function runEngine(executable, args, options = {}) {
|
|
29062
|
-
return new Promise((resolve) => {
|
|
29063
|
-
const log = new GameLog;
|
|
29064
|
-
let failure = null;
|
|
29065
|
-
const signal = callSignal();
|
|
29066
|
-
const child = spawnHidden(executable, args, {
|
|
29067
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
29068
|
-
...signal === undefined ? {} : { signal }
|
|
29069
|
-
});
|
|
29070
|
-
child.stdout.on("data", (chunk) => {
|
|
29071
|
-
log.append("stdout", chunk);
|
|
29072
|
-
});
|
|
29073
|
-
child.stderr.on("data", (chunk) => {
|
|
29074
|
-
log.append("stderr", chunk);
|
|
29075
|
-
});
|
|
29076
|
-
const { timeout } = options;
|
|
29077
|
-
const timer = timeout === undefined ? undefined : setTimeout(() => {
|
|
29078
|
-
failure ??= `it ran past its ${timeout.said}`;
|
|
29079
|
-
child.kill();
|
|
29080
|
-
}, timeout.ms);
|
|
29081
|
-
child.on("error", (error) => {
|
|
29082
|
-
failure ??= error.name === "AbortError" ? "the call was cancelled" : `it could not be started: ${error.message}`;
|
|
29083
|
-
});
|
|
29084
|
-
child.on("close", (code, closedBy) => {
|
|
29085
|
-
clearTimeout(timer);
|
|
29086
|
-
log.finish();
|
|
29087
|
-
const started = child.pid !== undefined;
|
|
29088
|
-
resolve({ log, exitCode: started ? code : null, exitSignal: closedBy, failure });
|
|
29089
|
-
});
|
|
29090
|
-
});
|
|
29091
|
-
}
|
|
29092
|
-
function howItEnded(run) {
|
|
29093
|
-
if (run.failure !== null) {
|
|
29094
|
-
return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
|
|
29095
|
-
}
|
|
29096
|
-
return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
|
|
29097
|
-
}
|
|
29098
|
-
|
|
29099
29174
|
// src/file-move.ts
|
|
29100
29175
|
import {
|
|
29101
|
-
existsSync as
|
|
29176
|
+
existsSync as existsSync13,
|
|
29102
29177
|
mkdirSync as mkdirSync10,
|
|
29103
|
-
readdirSync as
|
|
29178
|
+
readdirSync as readdirSync6,
|
|
29104
29179
|
readFileSync as readFileSync16,
|
|
29105
29180
|
renameSync as renameSync3,
|
|
29106
|
-
statSync as
|
|
29181
|
+
statSync as statSync7,
|
|
29107
29182
|
writeFileSync as writeFileSync11
|
|
29108
29183
|
} from "node:fs";
|
|
29109
|
-
import { basename as
|
|
29184
|
+
import { basename as basename4, dirname as dirname12, extname as extname3, join as join16, relative as relative4 } from "node:path";
|
|
29110
29185
|
|
|
29111
29186
|
// src/project-scan.ts
|
|
29112
|
-
import { existsSync as
|
|
29113
|
-
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";
|
|
29114
29189
|
var SKIPPED = new Set(["node_modules"]);
|
|
29115
29190
|
function skipped(name) {
|
|
29116
29191
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
@@ -29120,10 +29195,10 @@ function steppedOver(directory) {
|
|
|
29120
29195
|
return gdignored(directory) || installedByUs(directory);
|
|
29121
29196
|
}
|
|
29122
29197
|
function gdignored(directory) {
|
|
29123
|
-
return
|
|
29198
|
+
return existsSync9(join13(directory, ".gdignore"));
|
|
29124
29199
|
}
|
|
29125
29200
|
function installedByUs(directory) {
|
|
29126
|
-
return
|
|
29201
|
+
return existsSync9(join13(directory, VERSION_MARKER));
|
|
29127
29202
|
}
|
|
29128
29203
|
var SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
29129
29204
|
var ASSET_EXTENSIONS = new Set([
|
|
@@ -29162,12 +29237,12 @@ function projectStructure(projectPath) {
|
|
|
29162
29237
|
if (steppedOver(directory)) {
|
|
29163
29238
|
return;
|
|
29164
29239
|
}
|
|
29165
|
-
for (const entry of
|
|
29240
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29166
29241
|
if (skipped(entry.name)) {
|
|
29167
29242
|
continue;
|
|
29168
29243
|
}
|
|
29169
29244
|
if (entry.isDirectory()) {
|
|
29170
|
-
visit(
|
|
29245
|
+
visit(join13(directory, entry.name));
|
|
29171
29246
|
} else if (entry.isFile()) {
|
|
29172
29247
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
29173
29248
|
if (SCENE_EXTENSIONS.has(extension)) {
|
|
@@ -29191,13 +29266,13 @@ function projectFiles(projectPath, extensions) {
|
|
|
29191
29266
|
if (steppedOver(directory)) {
|
|
29192
29267
|
return;
|
|
29193
29268
|
}
|
|
29194
|
-
for (const entry of
|
|
29269
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29195
29270
|
if (skipped(entry.name)) {
|
|
29196
29271
|
continue;
|
|
29197
29272
|
}
|
|
29198
29273
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
29199
29274
|
if (entry.isDirectory()) {
|
|
29200
|
-
visit(
|
|
29275
|
+
visit(join13(directory, entry.name), spelled);
|
|
29201
29276
|
} else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
|
|
29202
29277
|
found.push(spelled);
|
|
29203
29278
|
}
|
|
@@ -29213,20 +29288,20 @@ function scriptsWithoutUid(projectPath) {
|
|
|
29213
29288
|
if (steppedOver(directory)) {
|
|
29214
29289
|
return;
|
|
29215
29290
|
}
|
|
29216
|
-
for (const entry of
|
|
29291
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29217
29292
|
if (skipped(entry.name)) {
|
|
29218
29293
|
continue;
|
|
29219
29294
|
}
|
|
29220
29295
|
const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
29221
29296
|
if (entry.isDirectory()) {
|
|
29222
|
-
visit(
|
|
29297
|
+
visit(join13(directory, entry.name), spelled);
|
|
29223
29298
|
continue;
|
|
29224
29299
|
}
|
|
29225
29300
|
if (!entry.isFile()) {
|
|
29226
29301
|
continue;
|
|
29227
29302
|
}
|
|
29228
29303
|
const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
29229
|
-
if (CARRIES_A_UID.has(extension) && !
|
|
29304
|
+
if (CARRIES_A_UID.has(extension) && !existsSync9(join13(directory, `${entry.name}.uid`))) {
|
|
29230
29305
|
found.push(spelled);
|
|
29231
29306
|
}
|
|
29232
29307
|
}
|
|
@@ -29254,11 +29329,11 @@ function projectTexts(projectPath) {
|
|
|
29254
29329
|
if (steppedOver(directory)) {
|
|
29255
29330
|
return;
|
|
29256
29331
|
}
|
|
29257
|
-
for (const entry of
|
|
29332
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29258
29333
|
if (skipped(entry.name)) {
|
|
29259
29334
|
continue;
|
|
29260
29335
|
}
|
|
29261
|
-
const absolute =
|
|
29336
|
+
const absolute = join13(directory, entry.name);
|
|
29262
29337
|
const spelled = `${prefix}${entry.name}`;
|
|
29263
29338
|
if (entry.isDirectory()) {
|
|
29264
29339
|
visit(absolute, `${spelled}/`);
|
|
@@ -29284,11 +29359,11 @@ function ignoredTexts(projectPath) {
|
|
|
29284
29359
|
return;
|
|
29285
29360
|
}
|
|
29286
29361
|
const under = ignored || gdignored(directory);
|
|
29287
|
-
for (const entry of
|
|
29362
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29288
29363
|
if (skipped(entry.name)) {
|
|
29289
29364
|
continue;
|
|
29290
29365
|
}
|
|
29291
|
-
const absolute =
|
|
29366
|
+
const absolute = join13(directory, entry.name);
|
|
29292
29367
|
const spelled = `${prefix}${entry.name}`;
|
|
29293
29368
|
if (entry.isDirectory()) {
|
|
29294
29369
|
visit(absolute, `${spelled}/`, under);
|
|
@@ -29321,14 +29396,14 @@ function searchProject(projectPath, options) {
|
|
|
29321
29396
|
if (steppedOver(directory)) {
|
|
29322
29397
|
return;
|
|
29323
29398
|
}
|
|
29324
|
-
for (const entry of
|
|
29399
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true })) {
|
|
29325
29400
|
if (full()) {
|
|
29326
29401
|
return;
|
|
29327
29402
|
}
|
|
29328
29403
|
if (skipped(entry.name)) {
|
|
29329
29404
|
continue;
|
|
29330
29405
|
}
|
|
29331
|
-
const entryPath =
|
|
29406
|
+
const entryPath = join13(directory, entry.name);
|
|
29332
29407
|
if (entry.isDirectory()) {
|
|
29333
29408
|
visit(entryPath);
|
|
29334
29409
|
continue;
|
|
@@ -29367,8 +29442,8 @@ function searchProject(projectPath, options) {
|
|
|
29367
29442
|
}
|
|
29368
29443
|
|
|
29369
29444
|
// src/rename.ts
|
|
29370
|
-
import { existsSync as
|
|
29371
|
-
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";
|
|
29372
29447
|
class Script {
|
|
29373
29448
|
path;
|
|
29374
29449
|
text;
|
|
@@ -30097,21 +30172,21 @@ function applyRewrites(rewrites) {
|
|
|
30097
30172
|
if (now !== rewrite.before) {
|
|
30098
30173
|
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
30099
30174
|
}
|
|
30100
|
-
if (rewrite.writeTo !== rewrite.absolute &&
|
|
30175
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync10(rewrite.writeTo)) {
|
|
30101
30176
|
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
30102
30177
|
}
|
|
30103
30178
|
}
|
|
30104
30179
|
const done = [];
|
|
30105
30180
|
try {
|
|
30106
30181
|
for (const rewrite of rewrites) {
|
|
30107
|
-
mkdirSync8(
|
|
30182
|
+
mkdirSync8(dirname9(rewrite.writeTo), { recursive: true });
|
|
30108
30183
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
30109
30184
|
writeFileSync9(staging, rewrite.after, "utf8");
|
|
30110
30185
|
renameSync2(staging, rewrite.writeTo);
|
|
30111
30186
|
let movedUid = false;
|
|
30112
30187
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
30113
30188
|
unlinkSync2(rewrite.absolute);
|
|
30114
|
-
if (
|
|
30189
|
+
if (existsSync10(`${rewrite.absolute}.uid`)) {
|
|
30115
30190
|
renameSync2(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
30116
30191
|
movedUid = true;
|
|
30117
30192
|
}
|
|
@@ -30135,14 +30210,14 @@ function applyRewrites(rewrites) {
|
|
|
30135
30210
|
}
|
|
30136
30211
|
|
|
30137
30212
|
// src/rename-symbol.ts
|
|
30138
|
-
import { existsSync as
|
|
30139
|
-
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";
|
|
30140
30215
|
|
|
30141
30216
|
// src/lsp_client.ts
|
|
30142
|
-
import { existsSync as
|
|
30217
|
+
import { existsSync as existsSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
30143
30218
|
import { readFile, realpath } from "node:fs/promises";
|
|
30144
30219
|
import { createConnection as createConnection3 } from "node:net";
|
|
30145
|
-
import { dirname as
|
|
30220
|
+
import { dirname as dirname10, join as join14, resolve as resolve3 } from "node:path";
|
|
30146
30221
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
30147
30222
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
30148
30223
|
var DEFAULT_LSP_PORT = 6005;
|
|
@@ -30340,7 +30415,7 @@ class GodotLSPClient {
|
|
|
30340
30415
|
if (this.initialized) {
|
|
30341
30416
|
return;
|
|
30342
30417
|
}
|
|
30343
|
-
const rootPath = this.rootPath ??
|
|
30418
|
+
const rootPath = this.rootPath ?? dirname10(resolve3(filePath));
|
|
30344
30419
|
await this.initialize(rootPath);
|
|
30345
30420
|
}
|
|
30346
30421
|
async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
|
|
@@ -30913,7 +30988,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
30913
30988
|
} catch {
|
|
30914
30989
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
30915
30990
|
}
|
|
30916
|
-
if (!
|
|
30991
|
+
if (!existsSync11(join14(projectPath, "project.godot"))) {
|
|
30917
30992
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
30918
30993
|
}
|
|
30919
30994
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -31288,13 +31363,13 @@ function onDiskSpelling(path) {
|
|
|
31288
31363
|
let at = resolve5(path);
|
|
31289
31364
|
for (;; ) {
|
|
31290
31365
|
try {
|
|
31291
|
-
return
|
|
31366
|
+
return join15(realpathSync4.native(at), ...missing.reverse());
|
|
31292
31367
|
} catch {
|
|
31293
|
-
const parent =
|
|
31368
|
+
const parent = dirname11(at);
|
|
31294
31369
|
if (parent === at) {
|
|
31295
31370
|
return resolve5(path);
|
|
31296
31371
|
}
|
|
31297
|
-
missing.push(
|
|
31372
|
+
missing.push(basename3(at));
|
|
31298
31373
|
at = parent;
|
|
31299
31374
|
}
|
|
31300
31375
|
}
|
|
@@ -31341,7 +31416,7 @@ async function renameSymbol(request, services) {
|
|
|
31341
31416
|
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
31342
31417
|
};
|
|
31343
31418
|
}
|
|
31344
|
-
if (
|
|
31419
|
+
if (existsSync12(request.newScriptPath)) {
|
|
31345
31420
|
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
31346
31421
|
}
|
|
31347
31422
|
}
|
|
@@ -31517,7 +31592,7 @@ async function renameSymbol(request, services) {
|
|
|
31517
31592
|
scripts,
|
|
31518
31593
|
moves: new Map([[declaringPath, moveTo]]),
|
|
31519
31594
|
named: [declaringPath],
|
|
31520
|
-
exists: (path) =>
|
|
31595
|
+
exists: (path) => existsSync12(`${projectPath}/${path.slice("res://".length)}`)
|
|
31521
31596
|
}, plan);
|
|
31522
31597
|
}
|
|
31523
31598
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
|
|
@@ -31718,15 +31793,15 @@ ${mention.kind}`;
|
|
|
31718
31793
|
var LEFT_ALONE_ANSWER_LINES = 300;
|
|
31719
31794
|
var detailWrites = 0;
|
|
31720
31795
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
31721
|
-
const directory =
|
|
31796
|
+
const directory = join15(projectPath, ".godot", "gdharness-renames");
|
|
31722
31797
|
mkdirSync9(directory, { recursive: true });
|
|
31723
31798
|
detailWrites += 1;
|
|
31724
|
-
const path =
|
|
31799
|
+
const path = join15(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
31725
31800
|
writeFileSync10(path, `${JSON.stringify(detail, null, 2)}
|
|
31726
31801
|
`, "utf8");
|
|
31727
|
-
const kept =
|
|
31802
|
+
const kept = readdirSync5(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
31728
31803
|
for (const old of kept.slice(10)) {
|
|
31729
|
-
rmSync7(
|
|
31804
|
+
rmSync7(join15(directory, old), { force: true });
|
|
31730
31805
|
}
|
|
31731
31806
|
return path;
|
|
31732
31807
|
}
|
|
@@ -31751,15 +31826,15 @@ function summaryOf(edits, mentions, rewrites, left) {
|
|
|
31751
31826
|
// src/file-move.ts
|
|
31752
31827
|
var SIDECARS = [".uid", ".import"];
|
|
31753
31828
|
function filesUnder(directory) {
|
|
31754
|
-
return
|
|
31755
|
-
const path =
|
|
31829
|
+
return readdirSync6(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
31830
|
+
const path = join16(directory, entry.name);
|
|
31756
31831
|
return entry.isDirectory() ? filesUnder(path) : [path];
|
|
31757
31832
|
});
|
|
31758
31833
|
}
|
|
31759
31834
|
function sameFile(one, other) {
|
|
31760
31835
|
try {
|
|
31761
|
-
const a =
|
|
31762
|
-
const b =
|
|
31836
|
+
const a = statSync7(one);
|
|
31837
|
+
const b = statSync7(other);
|
|
31763
31838
|
return a.ino === b.ino && a.dev === b.dev;
|
|
31764
31839
|
} catch {
|
|
31765
31840
|
return false;
|
|
@@ -31773,8 +31848,8 @@ function unseenByTheEngine(projectPath, path) {
|
|
|
31773
31848
|
return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
|
|
31774
31849
|
}
|
|
31775
31850
|
if (at < steps.length - 1) {
|
|
31776
|
-
directory =
|
|
31777
|
-
if (
|
|
31851
|
+
directory = join16(directory, step);
|
|
31852
|
+
if (existsSync13(join16(directory, ".gdignore"))) {
|
|
31778
31853
|
return `${path} is under a directory holding a .gdignore, which the engine does not load`;
|
|
31779
31854
|
}
|
|
31780
31855
|
}
|
|
@@ -31786,21 +31861,21 @@ function installedAddonOf(projectPath, path) {
|
|
|
31786
31861
|
if (steps[0] !== "addons" || steps[1] === undefined) {
|
|
31787
31862
|
return null;
|
|
31788
31863
|
}
|
|
31789
|
-
return
|
|
31864
|
+
return existsSync13(join16(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
|
|
31790
31865
|
}
|
|
31791
31866
|
function binaryResources(projectPath) {
|
|
31792
31867
|
const found = [];
|
|
31793
31868
|
const visit = (directory, prefix) => {
|
|
31794
|
-
if (
|
|
31869
|
+
if (existsSync13(join16(directory, ".gdignore"))) {
|
|
31795
31870
|
return;
|
|
31796
31871
|
}
|
|
31797
|
-
for (const entry of
|
|
31872
|
+
for (const entry of readdirSync6(directory, { withFileTypes: true })) {
|
|
31798
31873
|
if (entry.name.startsWith(".")) {
|
|
31799
31874
|
continue;
|
|
31800
31875
|
}
|
|
31801
31876
|
const path = `${prefix}${entry.name}`;
|
|
31802
31877
|
if (entry.isDirectory()) {
|
|
31803
|
-
visit(
|
|
31878
|
+
visit(join16(directory, entry.name), `${path}/`);
|
|
31804
31879
|
} else if (/\.(scn|res)$/i.test(entry.name)) {
|
|
31805
31880
|
found.push(path);
|
|
31806
31881
|
}
|
|
@@ -31810,7 +31885,7 @@ function binaryResources(projectPath) {
|
|
|
31810
31885
|
return found;
|
|
31811
31886
|
}
|
|
31812
31887
|
function targetPathOf(projectPath, absolute) {
|
|
31813
|
-
const inside = relative4(onDiskSpelling(projectPath),
|
|
31888
|
+
const inside = relative4(onDiskSpelling(projectPath), join16(onDiskSpelling(dirname12(absolute)), basename4(absolute)));
|
|
31814
31889
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
31815
31890
|
return null;
|
|
31816
31891
|
}
|
|
@@ -31828,13 +31903,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31828
31903
|
if (to === null) {
|
|
31829
31904
|
return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
|
|
31830
31905
|
}
|
|
31831
|
-
if (!
|
|
31906
|
+
if (!existsSync13(absoluteFrom)) {
|
|
31832
31907
|
return { ok: false, reason: `There is nothing at ${from} to move.` };
|
|
31833
31908
|
}
|
|
31834
31909
|
if (from === to) {
|
|
31835
31910
|
return { ok: false, reason: `${from} is already there; nothing to move.` };
|
|
31836
31911
|
}
|
|
31837
|
-
const isDirectory =
|
|
31912
|
+
const isDirectory = statSync7(absoluteFrom).isDirectory();
|
|
31838
31913
|
if (from === "res://project.godot") {
|
|
31839
31914
|
return {
|
|
31840
31915
|
ok: false,
|
|
@@ -31857,7 +31932,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31857
31932
|
if (isDirectory && to.startsWith(`${from}/`)) {
|
|
31858
31933
|
return { ok: false, reason: `${from} cannot be moved into itself.` };
|
|
31859
31934
|
}
|
|
31860
|
-
if (
|
|
31935
|
+
if (existsSync13(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
|
|
31861
31936
|
return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
|
|
31862
31937
|
}
|
|
31863
31938
|
const unseen = unseenByTheEngine(projectPath, to);
|
|
@@ -31865,7 +31940,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31865
31940
|
return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
|
|
31866
31941
|
}
|
|
31867
31942
|
if (!isDirectory) {
|
|
31868
|
-
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))));
|
|
31869
31944
|
if (owner !== undefined && owner !== null) {
|
|
31870
31945
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
31871
31946
|
}
|
|
@@ -31887,7 +31962,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31887
31962
|
} else {
|
|
31888
31963
|
moves.set(from, to);
|
|
31889
31964
|
for (const sidecar of SIDECARS) {
|
|
31890
|
-
if (
|
|
31965
|
+
if (existsSync13(`${absoluteFrom}${sidecar}`)) {
|
|
31891
31966
|
moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
|
|
31892
31967
|
}
|
|
31893
31968
|
}
|
|
@@ -31899,12 +31974,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31899
31974
|
scripts,
|
|
31900
31975
|
moves,
|
|
31901
31976
|
named: [from],
|
|
31902
|
-
exists: (path) =>
|
|
31977
|
+
exists: (path) => existsSync13(join16(projectPath, path.slice("res://".length)))
|
|
31903
31978
|
}, { edits: [], mentions: [] });
|
|
31904
31979
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
|
|
31905
31980
|
const binaries = [];
|
|
31906
31981
|
for (const path of binaryResources(projectPath)) {
|
|
31907
|
-
const bytes = readFileSync16(
|
|
31982
|
+
const bytes = readFileSync16(join16(projectPath, path.slice("res://".length)));
|
|
31908
31983
|
const names = [...moves.keys()].find((moving) => bytes.includes(moving));
|
|
31909
31984
|
if (names !== undefined) {
|
|
31910
31985
|
binaries.push({
|
|
@@ -31923,7 +31998,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
31923
31998
|
}));
|
|
31924
31999
|
const left = leftAloneReport(mentions);
|
|
31925
32000
|
const rewrites = rewritesOf(plan, texts, null);
|
|
31926
|
-
const absoluteOf = (path) =>
|
|
32001
|
+
const absoluteOf = (path) => join16(projectPath, path.slice("res://".length));
|
|
31927
32002
|
const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
|
|
31928
32003
|
const moved = [...moves].map(([source, target]) => {
|
|
31929
32004
|
const rewrite = rewritten.get(source);
|
|
@@ -31970,19 +32045,19 @@ function applyFileMove(outcome) {
|
|
|
31970
32045
|
}
|
|
31971
32046
|
}
|
|
31972
32047
|
for (const file of outcome.moved) {
|
|
31973
|
-
if (!
|
|
32048
|
+
if (!existsSync13(file.absoluteFrom)) {
|
|
31974
32049
|
throw new Error(`${file.from} is gone from disk; nothing was written`);
|
|
31975
32050
|
}
|
|
31976
32051
|
if (file.text !== null && readFileSync16(file.absoluteFrom, "utf8") !== file.text.before) {
|
|
31977
32052
|
throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
|
|
31978
32053
|
}
|
|
31979
|
-
if (
|
|
32054
|
+
if (existsSync13(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
|
|
31980
32055
|
throw new Error(`${file.to} already exists; nothing was written`);
|
|
31981
32056
|
}
|
|
31982
32057
|
}
|
|
31983
32058
|
const undo = [];
|
|
31984
32059
|
const rename = (source, target) => {
|
|
31985
|
-
mkdirSync10(
|
|
32060
|
+
mkdirSync10(dirname12(target), { recursive: true });
|
|
31986
32061
|
renameSync3(source, target);
|
|
31987
32062
|
undo.push(() => {
|
|
31988
32063
|
renameSync3(target, source);
|
|
@@ -32705,27 +32780,27 @@ function getDefaultBridge(ownProject = null) {
|
|
|
32705
32780
|
}
|
|
32706
32781
|
|
|
32707
32782
|
// src/godot-path.ts
|
|
32708
|
-
import { existsSync as
|
|
32783
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
32709
32784
|
import { normalize } from "node:path";
|
|
32710
32785
|
|
|
32711
32786
|
// src/detection.ts
|
|
32712
|
-
import { existsSync as
|
|
32713
|
-
import { homedir as
|
|
32714
|
-
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";
|
|
32715
32790
|
function resolveHomeDirectory() {
|
|
32716
32791
|
try {
|
|
32717
|
-
return
|
|
32792
|
+
return homedir3();
|
|
32718
32793
|
} catch {
|
|
32719
32794
|
return "";
|
|
32720
32795
|
}
|
|
32721
32796
|
}
|
|
32722
32797
|
function scanDirectoryForGodotBinaries(directory, platform) {
|
|
32723
|
-
if (!directory || !
|
|
32798
|
+
if (!directory || !existsSync14(directory)) {
|
|
32724
32799
|
return [];
|
|
32725
32800
|
}
|
|
32726
32801
|
let entries;
|
|
32727
32802
|
try {
|
|
32728
|
-
entries =
|
|
32803
|
+
entries = readdirSync7(directory);
|
|
32729
32804
|
} catch {
|
|
32730
32805
|
return [];
|
|
32731
32806
|
}
|
|
@@ -32735,16 +32810,16 @@ function scanDirectoryForGodotBinaries(directory, platform) {
|
|
|
32735
32810
|
if (!pattern.test(name)) {
|
|
32736
32811
|
continue;
|
|
32737
32812
|
}
|
|
32738
|
-
const fullPath =
|
|
32813
|
+
const fullPath = join17(directory, name);
|
|
32739
32814
|
try {
|
|
32740
|
-
const stat =
|
|
32815
|
+
const stat = statSync8(fullPath);
|
|
32741
32816
|
if (stat.isFile()) {
|
|
32742
32817
|
matches.push({ name, mtime: stat.mtimeMs });
|
|
32743
32818
|
}
|
|
32744
32819
|
} catch {}
|
|
32745
32820
|
}
|
|
32746
32821
|
matches.sort((a, b) => b.mtime - a.mtime);
|
|
32747
|
-
return matches.map((m) =>
|
|
32822
|
+
return matches.map((m) => join17(directory, m.name));
|
|
32748
32823
|
}
|
|
32749
32824
|
function conventionalPaths(platform, home) {
|
|
32750
32825
|
const paths = ["godot"];
|
|
@@ -32845,7 +32920,7 @@ class GodotLocator {
|
|
|
32845
32920
|
return known;
|
|
32846
32921
|
}
|
|
32847
32922
|
let reason;
|
|
32848
|
-
if (path !== "godot" && !
|
|
32923
|
+
if (path !== "godot" && !existsSync15(path)) {
|
|
32849
32924
|
reason = "does not exist";
|
|
32850
32925
|
} else {
|
|
32851
32926
|
try {
|
|
@@ -32861,12 +32936,12 @@ class GodotLocator {
|
|
|
32861
32936
|
}
|
|
32862
32937
|
|
|
32863
32938
|
// src/headless.ts
|
|
32864
|
-
import { existsSync as
|
|
32865
|
-
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";
|
|
32866
32941
|
|
|
32867
32942
|
// src/library-copies.ts
|
|
32868
|
-
import { existsSync as
|
|
32869
|
-
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";
|
|
32870
32945
|
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
32871
32946
|
const setAside = [];
|
|
32872
32947
|
const swept = [];
|
|
@@ -32874,26 +32949,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
32874
32949
|
return { setAside, swept };
|
|
32875
32950
|
}
|
|
32876
32951
|
for (const library of windowsLibraries(projectPath)) {
|
|
32877
|
-
const folder =
|
|
32878
|
-
const copy = `~${
|
|
32879
|
-
if (
|
|
32880
|
-
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()}`);
|
|
32881
32956
|
try {
|
|
32882
|
-
renameSync4(
|
|
32957
|
+
renameSync4(join18(folder, copy), aside);
|
|
32883
32958
|
setAside.push(aside);
|
|
32884
32959
|
} catch {}
|
|
32885
32960
|
}
|
|
32886
32961
|
let names;
|
|
32887
32962
|
try {
|
|
32888
|
-
names =
|
|
32963
|
+
names = readdirSync8(folder);
|
|
32889
32964
|
} catch {
|
|
32890
32965
|
continue;
|
|
32891
32966
|
}
|
|
32892
32967
|
for (const name of names) {
|
|
32893
32968
|
if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
|
|
32894
32969
|
try {
|
|
32895
|
-
unlinkSync3(
|
|
32896
|
-
swept.push(
|
|
32970
|
+
unlinkSync3(join18(folder, name));
|
|
32971
|
+
swept.push(join18(folder, name));
|
|
32897
32972
|
} catch {}
|
|
32898
32973
|
}
|
|
32899
32974
|
}
|
|
@@ -32901,7 +32976,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
32901
32976
|
return { setAside, swept };
|
|
32902
32977
|
}
|
|
32903
32978
|
function windowsLibraries(projectPath) {
|
|
32904
|
-
const listed = readOrNull(
|
|
32979
|
+
const listed = readOrNull(join18(projectPath, ".godot", "extension_list.cfg"));
|
|
32905
32980
|
if (listed === null) {
|
|
32906
32981
|
return [];
|
|
32907
32982
|
}
|
|
@@ -32920,7 +32995,7 @@ function windowsLibraries(projectPath) {
|
|
|
32920
32995
|
continue;
|
|
32921
32996
|
}
|
|
32922
32997
|
const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
|
|
32923
|
-
const library = named === undefined ? null : resolveResource(projectPath,
|
|
32998
|
+
const library = named === undefined ? null : resolveResource(projectPath, dirname13(manifest), named);
|
|
32924
32999
|
if (library !== null) {
|
|
32925
33000
|
libraries.add(library);
|
|
32926
33001
|
}
|
|
@@ -32933,9 +33008,9 @@ function resolveResource(projectPath, relativeTo, path) {
|
|
|
32933
33008
|
return null;
|
|
32934
33009
|
}
|
|
32935
33010
|
if (path.startsWith("res://")) {
|
|
32936
|
-
return
|
|
33011
|
+
return join18(projectPath, path.slice("res://".length));
|
|
32937
33012
|
}
|
|
32938
|
-
return
|
|
33013
|
+
return isAbsolute4(path) ? path : join18(relativeTo, path);
|
|
32939
33014
|
}
|
|
32940
33015
|
function readOrNull(path) {
|
|
32941
33016
|
try {
|
|
@@ -32947,29 +33022,29 @@ function readOrNull(path) {
|
|
|
32947
33022
|
|
|
32948
33023
|
// src/reimport.ts
|
|
32949
33024
|
import { createHash as createHash3 } from "node:crypto";
|
|
32950
|
-
import { existsSync as
|
|
32951
|
-
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";
|
|
32952
33027
|
function forceNextImport(projectPath, resourcePath) {
|
|
32953
33028
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
32954
|
-
const sidecar =
|
|
32955
|
-
if (!
|
|
33029
|
+
const sidecar = join19(projectPath, `${relative}.import`);
|
|
33030
|
+
if (!existsSync17(sidecar)) {
|
|
32956
33031
|
return;
|
|
32957
33032
|
}
|
|
32958
|
-
const { atime, mtimeMs } =
|
|
33033
|
+
const { atime, mtimeMs } = statSync9(sidecar);
|
|
32959
33034
|
writeFileSync12(sidecar, readFileSync18(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
32960
33035
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
32961
33036
|
rmSync8(importRecord(projectPath, resourcePath), { force: true });
|
|
32962
33037
|
}
|
|
32963
33038
|
function importRecord(projectPath, resourcePath) {
|
|
32964
33039
|
const digest = createHash3("md5").update(resourcePath).digest("hex");
|
|
32965
|
-
return
|
|
33040
|
+
return join19(projectPath, ".godot", "imported", `${basename6(resourcePath)}-${digest}.md5`);
|
|
32966
33041
|
}
|
|
32967
33042
|
|
|
32968
33043
|
// src/scratch.ts
|
|
32969
|
-
import { existsSync as
|
|
33044
|
+
import { existsSync as existsSync18, mkdtempSync as mkdtempSync2, readdirSync as readdirSync9, rmSync as rmSync9 } from "node:fs";
|
|
32970
33045
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
32971
|
-
import { join as
|
|
32972
|
-
import
|
|
33046
|
+
import { join as join20 } from "node:path";
|
|
33047
|
+
import process8 from "node:process";
|
|
32973
33048
|
var SWEPT = { recursive: true, force: true };
|
|
32974
33049
|
var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
|
|
32975
33050
|
var waiting = new Set;
|
|
@@ -32984,12 +33059,12 @@ var KINDS = [
|
|
|
32984
33059
|
"tests"
|
|
32985
33060
|
];
|
|
32986
33061
|
function scratchDirectory(kind) {
|
|
32987
|
-
return mkdtempSync2(
|
|
33062
|
+
return mkdtempSync2(join20(tmpdir5(), `gdharness-${kind}-${process8.pid}-`));
|
|
32988
33063
|
}
|
|
32989
33064
|
function sweepAbandonedScratch(directory = tmpdir5()) {
|
|
32990
33065
|
let entries;
|
|
32991
33066
|
try {
|
|
32992
|
-
entries =
|
|
33067
|
+
entries = readdirSync9(directory);
|
|
32993
33068
|
} catch {
|
|
32994
33069
|
return;
|
|
32995
33070
|
}
|
|
@@ -33000,7 +33075,7 @@ function sweepAbandonedScratch(directory = tmpdir5()) {
|
|
|
33000
33075
|
continue;
|
|
33001
33076
|
}
|
|
33002
33077
|
try {
|
|
33003
|
-
remove(
|
|
33078
|
+
remove(join20(directory, entry));
|
|
33004
33079
|
} catch {}
|
|
33005
33080
|
}
|
|
33006
33081
|
}
|
|
@@ -33038,7 +33113,7 @@ function tryAgain(path, attempt) {
|
|
|
33038
33113
|
lastTryOnExit();
|
|
33039
33114
|
}
|
|
33040
33115
|
const after = AGAIN_AFTER_MS[attempt];
|
|
33041
|
-
if (after === undefined || !
|
|
33116
|
+
if (after === undefined || !existsSync18(path)) {
|
|
33042
33117
|
waiting.delete(path);
|
|
33043
33118
|
if (after === undefined) {
|
|
33044
33119
|
atExit.add(path);
|
|
@@ -33060,7 +33135,7 @@ function lastTryOnExit() {
|
|
|
33060
33135
|
return;
|
|
33061
33136
|
}
|
|
33062
33137
|
lastTryWatched = true;
|
|
33063
|
-
|
|
33138
|
+
process8.once("exit", () => {
|
|
33064
33139
|
for (const path of [...waiting, ...atExit]) {
|
|
33065
33140
|
try {
|
|
33066
33141
|
remove(path);
|
|
@@ -33138,7 +33213,7 @@ function refusalsIn(log) {
|
|
|
33138
33213
|
}
|
|
33139
33214
|
function scriptFault(problems, script) {
|
|
33140
33215
|
const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
|
|
33141
|
-
const directories = [...new Set([spelled(
|
|
33216
|
+
const directories = [...new Set([spelled(dirname14(script)), spelled(resolvedOr(dirname14(script)))])];
|
|
33142
33217
|
return problems.find((entry) => {
|
|
33143
33218
|
const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
|
|
33144
33219
|
return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
|
|
@@ -33176,26 +33251,33 @@ async function runImport(godotPath, projectPath, options = {}) {
|
|
|
33176
33251
|
};
|
|
33177
33252
|
const first = await pass();
|
|
33178
33253
|
const collided = librariesNotCopied(first.messages);
|
|
33179
|
-
|
|
33254
|
+
const crash = first.docCacheNote;
|
|
33255
|
+
if (collided.length === 0 && crash === undefined) {
|
|
33180
33256
|
return first;
|
|
33181
33257
|
}
|
|
33182
|
-
const
|
|
33258
|
+
const { docCacheNote: _, ...again } = await pass();
|
|
33259
|
+
const second = again.ok || crash === undefined ? again : { ...again, message: `${again.message.replace(/[.\s]+$/, "")}. ${crash}` };
|
|
33183
33260
|
const still = librariesNotCopied(second.messages);
|
|
33184
33261
|
return {
|
|
33185
33262
|
...second,
|
|
33186
|
-
|
|
33187
|
-
|
|
33188
|
-
|
|
33263
|
+
...collided.length === 0 ? {} : {
|
|
33264
|
+
librariesRetried: collided,
|
|
33265
|
+
...still.length === 0 ? {} : { librariesNotLoaded: still },
|
|
33266
|
+
extensionNote: extensionRetryNote(collided, still)
|
|
33267
|
+
},
|
|
33268
|
+
...crash === undefined ? {} : { docCacheNote: crash }
|
|
33189
33269
|
};
|
|
33190
33270
|
}
|
|
33191
33271
|
async function importOnce(godotPath, projectPath) {
|
|
33192
33272
|
const logDir = scratchDirectory("import");
|
|
33273
|
+
const caches = editorCacheDirectory(godotPath);
|
|
33274
|
+
const cachesBefore = docCachesWritten(caches);
|
|
33193
33275
|
let ran;
|
|
33194
33276
|
try {
|
|
33195
33277
|
ran = await runEngine(godotPath, [
|
|
33196
33278
|
"--headless",
|
|
33197
33279
|
"--log-file",
|
|
33198
|
-
|
|
33280
|
+
join21(logDir, "engine.log"),
|
|
33199
33281
|
"--path",
|
|
33200
33282
|
projectPath,
|
|
33201
33283
|
"--import"
|
|
@@ -33204,24 +33286,26 @@ async function importOnce(godotPath, projectPath) {
|
|
|
33204
33286
|
discard(logDir);
|
|
33205
33287
|
}
|
|
33206
33288
|
const problems = problemsIn(ran.log);
|
|
33289
|
+
const crash = docCacheCrash(ran, cachesBefore, docCachesWritten(caches), "import");
|
|
33207
33290
|
if (ran.exitCode === 0 && ran.failure === null) {
|
|
33208
|
-
return { ok: true, ...reported2(problems) };
|
|
33291
|
+
return { ok: true, ...reported2(problems), ...crash === null ? {} : { docCacheNote: crash } };
|
|
33209
33292
|
}
|
|
33210
33293
|
return {
|
|
33211
33294
|
ok: false,
|
|
33212
33295
|
message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
|
|
33213
|
-
...reported2(problems)
|
|
33296
|
+
...reported2(problems),
|
|
33297
|
+
...crash === null ? {} : { docCacheNote: crash }
|
|
33214
33298
|
};
|
|
33215
33299
|
}
|
|
33216
33300
|
async function runOperation(engine, operation, params, projectPath) {
|
|
33217
33301
|
const paramsDir = scratchDirectory("params");
|
|
33218
|
-
const paramsFile =
|
|
33219
|
-
const answerFile =
|
|
33302
|
+
const paramsFile = join21(paramsDir, `${operation}.json`);
|
|
33303
|
+
const answerFile = join21(paramsDir, "answer.json");
|
|
33220
33304
|
writeFileSync13(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
33221
33305
|
const args = [
|
|
33222
33306
|
"--headless",
|
|
33223
33307
|
"--log-file",
|
|
33224
|
-
|
|
33308
|
+
join21(paramsDir, "engine.log"),
|
|
33225
33309
|
"--path",
|
|
33226
33310
|
projectPath,
|
|
33227
33311
|
"--script",
|
|
@@ -33309,7 +33393,7 @@ async function runOperation(engine, operation, params, projectPath) {
|
|
|
33309
33393
|
};
|
|
33310
33394
|
}
|
|
33311
33395
|
function answerIn(file) {
|
|
33312
|
-
if (!
|
|
33396
|
+
if (!existsSync19(file)) {
|
|
33313
33397
|
return { kind: "none" };
|
|
33314
33398
|
}
|
|
33315
33399
|
const text = readFileSync19(file, "utf8");
|
|
@@ -33935,26 +34019,26 @@ function askTheKeeper(address, line, withinMs) {
|
|
|
33935
34019
|
}
|
|
33936
34020
|
|
|
33937
34021
|
// src/lifecycle-log.ts
|
|
33938
|
-
import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync5, statSync as
|
|
33939
|
-
import { dirname as
|
|
33940
|
-
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";
|
|
33941
34025
|
function lifecycleLogPath() {
|
|
33942
|
-
return
|
|
34026
|
+
return join22(runtimeDirectory(), "servers.log");
|
|
33943
34027
|
}
|
|
33944
34028
|
var LIFECYCLE_LOG_LIMIT = 1e6;
|
|
33945
34029
|
function writeLifecycle(event, fields = {}) {
|
|
33946
34030
|
const path = lifecycleLogPath();
|
|
33947
34031
|
try {
|
|
33948
|
-
mkdirSync11(
|
|
33949
|
-
if ((
|
|
34032
|
+
mkdirSync11(dirname15(path), { recursive: true });
|
|
34033
|
+
if ((statSync10(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
33950
34034
|
try {
|
|
33951
34035
|
renameSync5(path, `${path}.1`);
|
|
33952
34036
|
} catch {}
|
|
33953
34037
|
}
|
|
33954
34038
|
const line = {
|
|
33955
34039
|
at: new Date().toISOString(),
|
|
33956
|
-
pid:
|
|
33957
|
-
ppid:
|
|
34040
|
+
pid: process9.pid,
|
|
34041
|
+
ppid: process9.ppid,
|
|
33958
34042
|
version: SERVER_VERSION,
|
|
33959
34043
|
event
|
|
33960
34044
|
};
|
|
@@ -34243,14 +34327,14 @@ function setupPromptHandlers(server) {
|
|
|
34243
34327
|
|
|
34244
34328
|
// src/restart-note.ts
|
|
34245
34329
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
|
|
34246
|
-
import { dirname as
|
|
34330
|
+
import { dirname as dirname16, join as join23 } from "node:path";
|
|
34247
34331
|
function restartNotePath(projectPath) {
|
|
34248
|
-
return
|
|
34332
|
+
return join23(projectPath, ".godot", "gdharness-restart.json");
|
|
34249
34333
|
}
|
|
34250
34334
|
function noteRestartBegun(note) {
|
|
34251
34335
|
const path = restartNotePath(note.projectPath);
|
|
34252
34336
|
try {
|
|
34253
|
-
mkdirSync12(
|
|
34337
|
+
mkdirSync12(dirname16(path), { recursive: true });
|
|
34254
34338
|
writeFileSync14(path, `${JSON.stringify(note, null, 2)}
|
|
34255
34339
|
`, "utf8");
|
|
34256
34340
|
} catch (error) {
|
|
@@ -34282,23 +34366,23 @@ function restartSettled(projectPath) {
|
|
|
34282
34366
|
|
|
34283
34367
|
// src/run-record.ts
|
|
34284
34368
|
import { createHash as createHash4 } from "node:crypto";
|
|
34285
|
-
import { mkdirSync as mkdirSync13, openSync as openSync3, readdirSync as
|
|
34286
|
-
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";
|
|
34287
34371
|
function runsDirectory() {
|
|
34288
|
-
return
|
|
34372
|
+
return join24(runtimeDirectory(), "runs");
|
|
34289
34373
|
}
|
|
34290
34374
|
function realAsFarAsItExists(path) {
|
|
34291
34375
|
const missing = [];
|
|
34292
34376
|
let at = path;
|
|
34293
34377
|
for (;; ) {
|
|
34294
34378
|
try {
|
|
34295
|
-
return
|
|
34379
|
+
return join24(realpathSync6.native(at), ...missing);
|
|
34296
34380
|
} catch {
|
|
34297
|
-
const parent =
|
|
34381
|
+
const parent = dirname17(at);
|
|
34298
34382
|
if (parent === at) {
|
|
34299
34383
|
return path;
|
|
34300
34384
|
}
|
|
34301
|
-
missing.unshift(
|
|
34385
|
+
missing.unshift(basename7(at));
|
|
34302
34386
|
at = parent;
|
|
34303
34387
|
}
|
|
34304
34388
|
}
|
|
@@ -34309,10 +34393,10 @@ function projectKey(projectPath) {
|
|
|
34309
34393
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
34310
34394
|
}
|
|
34311
34395
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
34312
|
-
return
|
|
34396
|
+
return join24(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
34313
34397
|
}
|
|
34314
34398
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
34315
|
-
return
|
|
34399
|
+
return join24(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
34316
34400
|
}
|
|
34317
34401
|
function writeLatestRun(servedBy, latest) {
|
|
34318
34402
|
try {
|
|
@@ -34338,17 +34422,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
34338
34422
|
var SHARED_NOTE = "run.json";
|
|
34339
34423
|
function openTranscript(startedAt) {
|
|
34340
34424
|
mkdirSync13(runsDirectory(), { recursive: true });
|
|
34341
|
-
const path =
|
|
34425
|
+
const path = join24(runsDirectory(), `run-${startedAt}.log`);
|
|
34342
34426
|
return { path, fd: openSync3(path, "a") };
|
|
34343
34427
|
}
|
|
34344
34428
|
function profilePath(startedAt) {
|
|
34345
|
-
return
|
|
34429
|
+
return join24(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
34430
|
+
}
|
|
34431
|
+
function profileMarkPath(startedAt) {
|
|
34432
|
+
return join24(runsDirectory(), `run-${startedAt}.profile-mark.json`);
|
|
34346
34433
|
}
|
|
34347
34434
|
function noteIsAbout(note, playing) {
|
|
34348
34435
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
34349
34436
|
}
|
|
34350
34437
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
34351
|
-
return
|
|
34438
|
+
return join24(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
34352
34439
|
}
|
|
34353
34440
|
function writeEditorRunNote(note) {
|
|
34354
34441
|
mkdirSync13(runsDirectory(), { recursive: true });
|
|
@@ -34359,7 +34446,7 @@ function writeEditorRunNote(note) {
|
|
|
34359
34446
|
}
|
|
34360
34447
|
function readEditorRunNote(projectPath) {
|
|
34361
34448
|
for (const directory of runtimeDirectories()) {
|
|
34362
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
34449
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join24(directory, "runs", "editor-run.json"));
|
|
34363
34450
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
34364
34451
|
return note;
|
|
34365
34452
|
}
|
|
@@ -34393,7 +34480,7 @@ function readRunRecord(projectPath) {
|
|
|
34393
34480
|
if (own !== null) {
|
|
34394
34481
|
return own;
|
|
34395
34482
|
}
|
|
34396
|
-
const shared = recordAt(
|
|
34483
|
+
const shared = recordAt(join24(directory, "runs", SHARED_NOTE));
|
|
34397
34484
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
34398
34485
|
return shared;
|
|
34399
34486
|
}
|
|
@@ -34405,13 +34492,13 @@ function everyRunRecord() {
|
|
|
34405
34492
|
for (const directory of runtimeDirectories()) {
|
|
34406
34493
|
let entries;
|
|
34407
34494
|
try {
|
|
34408
|
-
entries =
|
|
34495
|
+
entries = readdirSync10(join24(directory, "runs"));
|
|
34409
34496
|
} catch {
|
|
34410
34497
|
continue;
|
|
34411
34498
|
}
|
|
34412
34499
|
for (const entry of entries) {
|
|
34413
34500
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
34414
|
-
const record = recordAt(
|
|
34501
|
+
const record = recordAt(join24(directory, "runs", entry));
|
|
34415
34502
|
if (record !== null) {
|
|
34416
34503
|
found.push(record);
|
|
34417
34504
|
}
|
|
@@ -34581,7 +34668,7 @@ function whyNotTheRun(record, running, needed) {
|
|
|
34581
34668
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
34582
34669
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
34583
34670
|
}
|
|
34584
|
-
const engine = record.command === undefined ? null :
|
|
34671
|
+
const engine = record.command === undefined ? null : basename7(record.command);
|
|
34585
34672
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
34586
34673
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
34587
34674
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -34620,7 +34707,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
34620
34707
|
}
|
|
34621
34708
|
function clearRunRecord(projectPath) {
|
|
34622
34709
|
for (const directory of runtimeDirectories()) {
|
|
34623
|
-
const shared =
|
|
34710
|
+
const shared = join24(directory, "runs", SHARED_NOTE);
|
|
34624
34711
|
const sharedRecord = recordAt(shared);
|
|
34625
34712
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
34626
34713
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -34632,7 +34719,7 @@ function clearRunRecord(projectPath) {
|
|
|
34632
34719
|
}
|
|
34633
34720
|
function ageMs(path, now) {
|
|
34634
34721
|
try {
|
|
34635
|
-
return now -
|
|
34722
|
+
return now - statSync11(path).mtimeMs;
|
|
34636
34723
|
} catch {
|
|
34637
34724
|
return 0;
|
|
34638
34725
|
}
|
|
@@ -34641,20 +34728,21 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
34641
34728
|
const directory = runsDirectory();
|
|
34642
34729
|
const kept = new Set(everyRunRecord().flatMap((record) => [
|
|
34643
34730
|
record.transcript,
|
|
34731
|
+
profileMarkPath(record.startedAt),
|
|
34644
34732
|
...record.profile === undefined ? [] : [record.profile]
|
|
34645
34733
|
]));
|
|
34646
34734
|
let entries;
|
|
34647
34735
|
try {
|
|
34648
|
-
entries =
|
|
34736
|
+
entries = readdirSync10(directory);
|
|
34649
34737
|
} catch {
|
|
34650
34738
|
return;
|
|
34651
34739
|
}
|
|
34652
34740
|
for (const entry of entries) {
|
|
34653
|
-
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"));
|
|
34654
34742
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
34655
34743
|
continue;
|
|
34656
34744
|
}
|
|
34657
|
-
const path =
|
|
34745
|
+
const path = join24(directory, entry);
|
|
34658
34746
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
34659
34747
|
continue;
|
|
34660
34748
|
}
|
|
@@ -34665,7 +34753,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
34665
34753
|
}
|
|
34666
34754
|
|
|
34667
34755
|
// src/script-origins.ts
|
|
34668
|
-
import { join as
|
|
34756
|
+
import { join as join25 } from "node:path";
|
|
34669
34757
|
var UNRESOLVED = /Could not resolve class "([A-Za-z_][A-Za-z0-9_]*)", because of a parser error/;
|
|
34670
34758
|
function unresolvedClassIn(message) {
|
|
34671
34759
|
return UNRESOLVED.exec(message)?.[1] ?? null;
|
|
@@ -34721,7 +34809,7 @@ function checkWithEngine(godotPath, projectPath) {
|
|
|
34721
34809
|
const ran = await runEngine(godotPath, [
|
|
34722
34810
|
"--headless",
|
|
34723
34811
|
"--log-file",
|
|
34724
|
-
|
|
34812
|
+
join25(logDir, "engine.log"),
|
|
34725
34813
|
"--path",
|
|
34726
34814
|
projectPath,
|
|
34727
34815
|
"--check-only",
|
|
@@ -34815,6 +34903,52 @@ function isCalibration(signature) {
|
|
|
34815
34903
|
const parts = signatureParts(signature);
|
|
34816
34904
|
return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
|
|
34817
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
|
+
}
|
|
34818
34952
|
function perCallCostOf(totals) {
|
|
34819
34953
|
for (const [signature, sum] of Object.entries(totals.functions)) {
|
|
34820
34954
|
const parts = signatureParts(signature);
|
|
@@ -35467,7 +35601,7 @@ var TOOL_SPECS = [
|
|
|
35467
35601
|
rootNodeType: {
|
|
35468
35602
|
type: "string",
|
|
35469
35603
|
ops: ["create"],
|
|
35470
|
-
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."
|
|
35471
35605
|
},
|
|
35472
35606
|
rootNodeName: {
|
|
35473
35607
|
type: "string",
|
|
@@ -35505,7 +35639,7 @@ var TOOL_SPECS = [
|
|
|
35505
35639
|
{
|
|
35506
35640
|
name: "scene_node",
|
|
35507
35641
|
title: "Scene nodes",
|
|
35508
|
-
description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, 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}`,
|
|
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}`,
|
|
35509
35643
|
parameters: {
|
|
35510
35644
|
projectPath: PROJECT_PATH2,
|
|
35511
35645
|
scenePath: SCENE_PATH,
|
|
@@ -35518,7 +35652,7 @@ var TOOL_SPECS = [
|
|
|
35518
35652
|
nodeType: {
|
|
35519
35653
|
type: "string",
|
|
35520
35654
|
ops: ["add"],
|
|
35521
|
-
description: 'add: the node class, such as "CharacterBody2D". Give this or instancePath.'
|
|
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.'
|
|
35522
35656
|
},
|
|
35523
35657
|
instancePath: {
|
|
35524
35658
|
type: "string",
|
|
@@ -35863,7 +35997,7 @@ var TOOL_SPECS = [
|
|
|
35863
35997
|
resourceType: {
|
|
35864
35998
|
type: "string",
|
|
35865
35999
|
ops: ["create"],
|
|
35866
|
-
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.'
|
|
35867
36001
|
},
|
|
35868
36002
|
properties: { ...PROPERTIES, ops: ["create", "modify"] },
|
|
35869
36003
|
script: {
|
|
@@ -36057,6 +36191,17 @@ var TOOL_SPECS = [
|
|
|
36057
36191
|
ops: ["profile"],
|
|
36058
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."
|
|
36059
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
|
+
},
|
|
36060
36205
|
limit: {
|
|
36061
36206
|
type: "integer",
|
|
36062
36207
|
minimum: 1,
|
|
@@ -36108,7 +36253,7 @@ var TOOL_SPECS = [
|
|
|
36108
36253
|
requires: []
|
|
36109
36254
|
}
|
|
36110
36255
|
},
|
|
36111
|
-
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.",
|
|
36112
36257
|
defaultOperation: "start"
|
|
36113
36258
|
},
|
|
36114
36259
|
{
|
|
@@ -36906,16 +37051,16 @@ function serverInstructions() {
|
|
|
36906
37051
|
import { createHash as createHash5 } from "node:crypto";
|
|
36907
37052
|
import {
|
|
36908
37053
|
cpSync,
|
|
36909
|
-
existsSync as
|
|
37054
|
+
existsSync as existsSync20,
|
|
36910
37055
|
mkdirSync as mkdirSync14,
|
|
36911
|
-
readdirSync as
|
|
37056
|
+
readdirSync as readdirSync11,
|
|
36912
37057
|
readFileSync as readFileSync23,
|
|
36913
37058
|
rmdirSync,
|
|
36914
37059
|
rmSync as rmSync12,
|
|
36915
|
-
statSync as
|
|
37060
|
+
statSync as statSync12,
|
|
36916
37061
|
writeFileSync as writeFileSync15
|
|
36917
37062
|
} from "node:fs";
|
|
36918
|
-
import { dirname as
|
|
37063
|
+
import { dirname as dirname18, join as join26 } from "node:path";
|
|
36919
37064
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
36920
37065
|
var ADDONS = ["gdharness_editor", "gdharness_runtime", "auto_reload"];
|
|
36921
37066
|
var EDITOR_PLUGINS = ["gdharness_editor", "auto_reload"];
|
|
@@ -36934,13 +37079,13 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
|
|
|
36934
37079
|
function addonsDigest(names, from) {
|
|
36935
37080
|
const hash = createHash5("sha256");
|
|
36936
37081
|
const walk = (directory, relative) => {
|
|
36937
|
-
for (const name of
|
|
37082
|
+
for (const name of readdirSync11(directory).sort()) {
|
|
36938
37083
|
if (name === VERSION_MARKER || name === DIGEST_MARKER) {
|
|
36939
37084
|
continue;
|
|
36940
37085
|
}
|
|
36941
|
-
const path =
|
|
37086
|
+
const path = join26(directory, name);
|
|
36942
37087
|
const inside = `${relative}/${name}`;
|
|
36943
|
-
if (
|
|
37088
|
+
if (statSync12(path).isDirectory()) {
|
|
36944
37089
|
walk(path, inside);
|
|
36945
37090
|
} else {
|
|
36946
37091
|
hash.update(`${inside}
|
|
@@ -36952,7 +37097,7 @@ function addonsDigest(names, from) {
|
|
|
36952
37097
|
}
|
|
36953
37098
|
};
|
|
36954
37099
|
for (const name of names) {
|
|
36955
|
-
walk(
|
|
37100
|
+
walk(join26(from, name), name);
|
|
36956
37101
|
}
|
|
36957
37102
|
return hash.digest("hex");
|
|
36958
37103
|
}
|
|
@@ -36985,19 +37130,19 @@ function installedRuntimeDigest(projectPath) {
|
|
|
36985
37130
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
36986
37131
|
}
|
|
36987
37132
|
function digestMarker(projectPath, addon) {
|
|
36988
|
-
const marker =
|
|
36989
|
-
if (!
|
|
37133
|
+
const marker = join26(projectPath, "addons", addon, DIGEST_MARKER);
|
|
37134
|
+
if (!existsSync20(marker)) {
|
|
36990
37135
|
return null;
|
|
36991
37136
|
}
|
|
36992
37137
|
const said = readFileSync23(marker, "utf8").trim();
|
|
36993
37138
|
return said === "" ? null : said;
|
|
36994
37139
|
}
|
|
36995
37140
|
function shippedAddonsDirectory() {
|
|
36996
|
-
return
|
|
37141
|
+
return join26(dirname18(fileURLToPath3(import.meta.url)), "godot", "addons");
|
|
36997
37142
|
}
|
|
36998
37143
|
function installedAddonVersion(projectPath) {
|
|
36999
|
-
const marker =
|
|
37000
|
-
if (!
|
|
37144
|
+
const marker = join26(projectPath, "addons", ADDONS[0], VERSION_MARKER);
|
|
37145
|
+
if (!existsSync20(marker)) {
|
|
37001
37146
|
return null;
|
|
37002
37147
|
}
|
|
37003
37148
|
const said = readFileSync23(marker, "utf8").trim();
|
|
@@ -37011,7 +37156,7 @@ function loaderAmong(projectPath, named) {
|
|
|
37011
37156
|
}
|
|
37012
37157
|
let source = null;
|
|
37013
37158
|
try {
|
|
37014
|
-
source = readFileSync23(
|
|
37159
|
+
source = readFileSync23(join26(projectPath, path), "utf8");
|
|
37015
37160
|
} catch {}
|
|
37016
37161
|
if (source?.includes(RUNTIME_AUTOLOAD.path)) {
|
|
37017
37162
|
return { name: entry, path, exists: true };
|
|
@@ -37053,10 +37198,10 @@ function enabledPlugins(settings) {
|
|
|
37053
37198
|
function inspectProject(projectPath) {
|
|
37054
37199
|
const problems = [];
|
|
37055
37200
|
const addons = ADDONS.map((name) => {
|
|
37056
|
-
const directory =
|
|
37057
|
-
const marker =
|
|
37058
|
-
const installed =
|
|
37059
|
-
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;
|
|
37060
37205
|
const current = version === SERVER_VERSION;
|
|
37061
37206
|
if (!installed) {
|
|
37062
37207
|
problems.push(`addons/${name} is not installed; run gdharness setup`);
|
|
@@ -37065,7 +37210,7 @@ function inspectProject(projectPath) {
|
|
|
37065
37210
|
}
|
|
37066
37211
|
return { name, installed, version, current };
|
|
37067
37212
|
});
|
|
37068
|
-
const settings = parseProjectGodot(readFileSync23(
|
|
37213
|
+
const settings = parseProjectGodot(readFileSync23(join26(projectPath, "project.godot"), "utf8"));
|
|
37069
37214
|
const pluginsEnabled = enabledPlugins(settings);
|
|
37070
37215
|
for (const name of EDITOR_PLUGINS) {
|
|
37071
37216
|
if (!pluginsEnabled.includes(name)) {
|
|
@@ -37081,7 +37226,7 @@ function inspectProject(projectPath) {
|
|
|
37081
37226
|
const tracked = trackedByGit(projectPath, [...named.values()]);
|
|
37082
37227
|
const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
|
|
37083
37228
|
for (const [name, path] of named) {
|
|
37084
|
-
if (!
|
|
37229
|
+
if (!existsSync20(join26(projectPath, path))) {
|
|
37085
37230
|
if (!addonMissing(path)) {
|
|
37086
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`);
|
|
37087
37232
|
}
|
|
@@ -37133,7 +37278,7 @@ var UPDATE_NOTICE_EVERY = 500;
|
|
|
37133
37278
|
var FEEDBACK_NOTICE_EVERY = 250;
|
|
37134
37279
|
var FAILED_BUDGET = 1e6;
|
|
37135
37280
|
var ANSWER_BUDGET = 4000000;
|
|
37136
|
-
var __dirname2 =
|
|
37281
|
+
var __dirname2 = dirname19(fileURLToPath4(import.meta.url));
|
|
37137
37282
|
var PARENT_ASKED_AFTER_MS = 5000;
|
|
37138
37283
|
function editorRestartTimeoutMs() {
|
|
37139
37284
|
return waitFromEnv("GDHARNESS_EDITOR_RESTART_TIMEOUT_MS", 90000);
|
|
@@ -37165,22 +37310,22 @@ function writtenFileRefusal(path, rule) {
|
|
|
37165
37310
|
if (/^(res|user):\/\//.test(path)) {
|
|
37166
37311
|
return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
|
|
37167
37312
|
}
|
|
37168
|
-
if (!
|
|
37313
|
+
if (!isAbsolute5(path)) {
|
|
37169
37314
|
return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
|
|
37170
37315
|
}
|
|
37171
37316
|
if (!rule.extensions.includes(extname4(path).toLowerCase())) {
|
|
37172
37317
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
37173
37318
|
}
|
|
37174
|
-
const directory = realPathOr(
|
|
37175
|
-
if (!
|
|
37176
|
-
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.`;
|
|
37177
37322
|
}
|
|
37178
|
-
const where =
|
|
37323
|
+
const where = join27(directory, basename8(path));
|
|
37179
37324
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
37180
37325
|
if (inside !== undefined) {
|
|
37181
37326
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
37182
37327
|
}
|
|
37183
|
-
const taken = rule.writes(where).find((file) =>
|
|
37328
|
+
const taken = rule.writes(where).find((file) => existsSync21(file));
|
|
37184
37329
|
if (taken !== undefined) {
|
|
37185
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.`;
|
|
37186
37331
|
}
|
|
@@ -37291,7 +37436,7 @@ function exited(run) {
|
|
|
37291
37436
|
return run.exitCode !== null || run.exitSignal !== null;
|
|
37292
37437
|
}
|
|
37293
37438
|
function howItExited(run) {
|
|
37294
|
-
return run.exitSignal === null ?
|
|
37439
|
+
return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}, with no exit code`;
|
|
37295
37440
|
}
|
|
37296
37441
|
function errorsUnread(run) {
|
|
37297
37442
|
return run.throughEditor && typeof run.errorReport !== "string";
|
|
@@ -37404,7 +37549,7 @@ function endedWithoutACode(run) {
|
|
|
37404
37549
|
function ourRunEnded(run, now) {
|
|
37405
37550
|
const which = `${run.throughEditor ? "played in the editor" : "started"}${run.pid === null ? "" : ` (pid ${run.pid})`}`;
|
|
37406
37551
|
const when = run.endedAt === undefined ? "" : ` ${Math.max(0, Math.round((now - run.endedAt) / 1000))}s ago`;
|
|
37407
|
-
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}`;
|
|
37408
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.`;
|
|
37409
37554
|
}
|
|
37410
37555
|
function stopVerdict(stop) {
|
|
@@ -37742,7 +37887,7 @@ function notRunNote(notRun, failFast) {
|
|
|
37742
37887
|
function exportPresetRefusal(projectPath, preset) {
|
|
37743
37888
|
let text;
|
|
37744
37889
|
try {
|
|
37745
|
-
text = readFileSync24(
|
|
37890
|
+
text = readFileSync24(join27(projectPath, "export_presets.cfg"), "utf8");
|
|
37746
37891
|
} catch {
|
|
37747
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.`;
|
|
37748
37893
|
}
|
|
@@ -37764,7 +37909,7 @@ function exportPresetRefusal(projectPath, preset) {
|
|
|
37764
37909
|
}
|
|
37765
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(", ")}.`;
|
|
37766
37911
|
}
|
|
37767
|
-
function exportAnswer(ending, asked, before, after, beside = []) {
|
|
37912
|
+
function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null) {
|
|
37768
37913
|
const { log } = ending;
|
|
37769
37914
|
const written = after !== null && after !== before;
|
|
37770
37915
|
const notCopied = librariesNotCopied(log.all);
|
|
@@ -37781,6 +37926,7 @@ function exportAnswer(ending, asked, before, after, beside = []) {
|
|
|
37781
37926
|
librariesNotLoaded: notCopied,
|
|
37782
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.`
|
|
37783
37928
|
},
|
|
37929
|
+
...docCacheNote === null ? {} : { docCacheNote },
|
|
37784
37930
|
...foldedEntriesAnswer(log)
|
|
37785
37931
|
};
|
|
37786
37932
|
if (verdict.exported) {
|
|
@@ -37805,9 +37951,9 @@ function writtenBeside(before, after, output, within) {
|
|
|
37805
37951
|
function filesIn(folder) {
|
|
37806
37952
|
const found = new Map;
|
|
37807
37953
|
try {
|
|
37808
|
-
for (const entry of
|
|
37954
|
+
for (const entry of readdirSync12(folder, { withFileTypes: true })) {
|
|
37809
37955
|
if (entry.isFile()) {
|
|
37810
|
-
found.set(entry.name,
|
|
37956
|
+
found.set(entry.name, statSync13(join27(folder, entry.name)).mtimeMs);
|
|
37811
37957
|
}
|
|
37812
37958
|
}
|
|
37813
37959
|
} catch {}
|
|
@@ -37841,7 +37987,7 @@ function unsavedInEditorNote(scripts) {
|
|
|
37841
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.`;
|
|
37842
37988
|
}
|
|
37843
37989
|
function editorAddonMissing(projectPath) {
|
|
37844
|
-
if (!
|
|
37990
|
+
if (!existsSync21(join27(projectPath, "project.godot"))) {
|
|
37845
37991
|
return null;
|
|
37846
37992
|
}
|
|
37847
37993
|
const report = inspectProject(projectPath);
|
|
@@ -38129,7 +38275,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
38129
38275
|
let projectGodot = null;
|
|
38130
38276
|
if (projectPath !== null) {
|
|
38131
38277
|
try {
|
|
38132
|
-
projectGodot = readFileSync24(
|
|
38278
|
+
projectGodot = readFileSync24(join27(projectPath, "project.godot"), "utf8");
|
|
38133
38279
|
} catch {}
|
|
38134
38280
|
}
|
|
38135
38281
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -38307,7 +38453,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
38307
38453
|
var ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd";
|
|
38308
38454
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
38309
38455
|
try {
|
|
38310
|
-
return settingKeys(readFileSync24(
|
|
38456
|
+
return settingKeys(readFileSync24(join27(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
38311
38457
|
} catch {
|
|
38312
38458
|
return true;
|
|
38313
38459
|
}
|
|
@@ -38322,13 +38468,13 @@ function uncaughtNote(uncaught) {
|
|
|
38322
38468
|
}
|
|
38323
38469
|
function elsewhereIn(projectPath, asked) {
|
|
38324
38470
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
38325
|
-
if (wanted === "" ||
|
|
38471
|
+
if (wanted === "" || existsSync21(join27(projectPath, wanted))) {
|
|
38326
38472
|
return "";
|
|
38327
38473
|
}
|
|
38328
38474
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
38329
38475
|
let near = [];
|
|
38330
38476
|
try {
|
|
38331
|
-
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()));
|
|
38332
38478
|
} catch {
|
|
38333
38479
|
return "";
|
|
38334
38480
|
}
|
|
@@ -38420,23 +38566,23 @@ function engineChecked(why) {
|
|
|
38420
38566
|
var EXTERNAL_MEMBER = 'Could not resolve external class member "';
|
|
38421
38567
|
function isDirectory(path) {
|
|
38422
38568
|
try {
|
|
38423
|
-
return
|
|
38569
|
+
return statSync13(path).isDirectory();
|
|
38424
38570
|
} catch {
|
|
38425
38571
|
return false;
|
|
38426
38572
|
}
|
|
38427
38573
|
}
|
|
38428
38574
|
function scriptsUnder(directory, prefix) {
|
|
38429
|
-
if (
|
|
38575
|
+
if (existsSync21(join27(directory, ".gdignore"))) {
|
|
38430
38576
|
return [];
|
|
38431
38577
|
}
|
|
38432
38578
|
const found = [];
|
|
38433
|
-
for (const entry of
|
|
38579
|
+
for (const entry of readdirSync12(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
38434
38580
|
if (entry.name.startsWith(".")) {
|
|
38435
38581
|
continue;
|
|
38436
38582
|
}
|
|
38437
38583
|
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
38438
38584
|
if (entry.isDirectory()) {
|
|
38439
|
-
found.push(...scriptsUnder(
|
|
38585
|
+
found.push(...scriptsUnder(join27(directory, entry.name), path));
|
|
38440
38586
|
} else if (entry.isFile() && entry.name.endsWith(".gd")) {
|
|
38441
38587
|
found.push(path);
|
|
38442
38588
|
}
|
|
@@ -38452,7 +38598,7 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
38452
38598
|
return false;
|
|
38453
38599
|
}
|
|
38454
38600
|
try {
|
|
38455
|
-
return readFileSync24(
|
|
38601
|
+
return readFileSync24(join27(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
38456
38602
|
} catch {
|
|
38457
38603
|
return false;
|
|
38458
38604
|
}
|
|
@@ -38478,7 +38624,7 @@ var STATIC_LIST_HINT = { ttlMs: 5 * 60000, cacheScope: "private" };
|
|
|
38478
38624
|
class GodotServer {
|
|
38479
38625
|
stdio = null;
|
|
38480
38626
|
locator = new GodotLocator;
|
|
38481
|
-
operationsScript =
|
|
38627
|
+
operationsScript = join27(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
38482
38628
|
godotBridge;
|
|
38483
38629
|
tools = buildToolDefinitions();
|
|
38484
38630
|
activeProcess = null;
|
|
@@ -38739,14 +38885,14 @@ class GodotServer {
|
|
|
38739
38885
|
}
|
|
38740
38886
|
noteTheStart() {
|
|
38741
38887
|
const cwd = process.cwd();
|
|
38742
|
-
const mcpJson =
|
|
38888
|
+
const mcpJson = join27(cwd, ".mcp.json");
|
|
38743
38889
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
38744
38890
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
38745
38891
|
writeLifecycle("started", {
|
|
38746
38892
|
argv: process.argv.slice(1),
|
|
38747
38893
|
cwd,
|
|
38748
38894
|
project: this.ownProject,
|
|
38749
|
-
mcpJson:
|
|
38895
|
+
mcpJson: existsSync21(mcpJson) ? mcpJson : null
|
|
38750
38896
|
});
|
|
38751
38897
|
if (predecessor !== null) {
|
|
38752
38898
|
writeLifecycle("replaced", { project: this.ownProject, predecessor });
|
|
@@ -38887,7 +39033,7 @@ class GodotServer {
|
|
|
38887
39033
|
return checked.response;
|
|
38888
39034
|
}
|
|
38889
39035
|
const named = args["projectPath"];
|
|
38890
|
-
if (typeof named === "string" &&
|
|
39036
|
+
if (typeof named === "string" && existsSync21(join27(named, "project.godot"))) {
|
|
38891
39037
|
this.lastProjectPath = named;
|
|
38892
39038
|
}
|
|
38893
39039
|
this.updates.refresh();
|
|
@@ -38923,7 +39069,7 @@ class GodotServer {
|
|
|
38923
39069
|
let notSaved;
|
|
38924
39070
|
try {
|
|
38925
39071
|
savedAt = answer.content.map((item, index) => {
|
|
38926
|
-
const file =
|
|
39072
|
+
const file = join27(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
|
|
38927
39073
|
writeFileSync16(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
|
|
38928
39074
|
return file;
|
|
38929
39075
|
});
|
|
@@ -39541,8 +39687,8 @@ class GodotServer {
|
|
|
39541
39687
|
if (path === undefined) {
|
|
39542
39688
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
39543
39689
|
}
|
|
39544
|
-
const file =
|
|
39545
|
-
if (!
|
|
39690
|
+
const file = join27(path, "project.godot");
|
|
39691
|
+
if (!existsSync21(file)) {
|
|
39546
39692
|
return {
|
|
39547
39693
|
ok: false,
|
|
39548
39694
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -39588,7 +39734,7 @@ class GodotServer {
|
|
|
39588
39734
|
}
|
|
39589
39735
|
runEnvironment(args) {
|
|
39590
39736
|
const savesIn = readNonEmptyString(args, "savesIn");
|
|
39591
|
-
if (savesIn !== undefined && !
|
|
39737
|
+
if (savesIn !== undefined && !isAbsolute5(savesIn)) {
|
|
39592
39738
|
return {
|
|
39593
39739
|
ok: false,
|
|
39594
39740
|
response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
|
|
@@ -39847,7 +39993,8 @@ class GodotServer {
|
|
|
39847
39993
|
uidsCreated: given,
|
|
39848
39994
|
stillWithoutUid: after,
|
|
39849
39995
|
note: uidsLeftNote(after.length),
|
|
39850
|
-
...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
|
|
39996
|
+
...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
|
|
39997
|
+
...imported.docCacheNote === undefined ? {} : { docCacheNote: imported.docCacheNote }
|
|
39851
39998
|
}
|
|
39852
39999
|
}, scanned));
|
|
39853
40000
|
}
|
|
@@ -40086,6 +40233,9 @@ class GodotServer {
|
|
|
40086
40233
|
if (imported.extensionNote !== undefined) {
|
|
40087
40234
|
extra["extensionNote"] = imported.extensionNote;
|
|
40088
40235
|
}
|
|
40236
|
+
if (imported.docCacheNote !== undefined) {
|
|
40237
|
+
extra["docCacheNote"] = imported.docCacheNote;
|
|
40238
|
+
}
|
|
40089
40239
|
}
|
|
40090
40240
|
const after = await this.operation("get_import_status", statusAsk, projectPath);
|
|
40091
40241
|
if (!after.ok) {
|
|
@@ -40202,7 +40352,7 @@ class GodotServer {
|
|
|
40202
40352
|
const name = application["config/name"];
|
|
40203
40353
|
const mainScene = application["run/main_scene"];
|
|
40204
40354
|
const info = {
|
|
40205
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
40355
|
+
name: typeof name === "string" && name !== "" ? name : basename8(project.value.path),
|
|
40206
40356
|
path: project.value.path,
|
|
40207
40357
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
40208
40358
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -40285,18 +40435,18 @@ class GodotServer {
|
|
|
40285
40435
|
if (!engine.ok) {
|
|
40286
40436
|
return engine.response;
|
|
40287
40437
|
}
|
|
40288
|
-
const folder =
|
|
40438
|
+
const folder = dirname19(output.absolutePath);
|
|
40289
40439
|
try {
|
|
40290
40440
|
mkdirSync15(folder, { recursive: true });
|
|
40291
40441
|
} catch (error) {
|
|
40292
|
-
return this.createErrorResponse(`Cannot create ${
|
|
40442
|
+
return this.createErrorResponse(`Cannot create ${dirname19(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
40293
40443
|
}
|
|
40294
40444
|
const debug = readBoolean(args, "debug") ?? false;
|
|
40295
40445
|
const exportLogs = scratchDirectory("export");
|
|
40296
40446
|
const exportArgs = [
|
|
40297
40447
|
"--headless",
|
|
40298
40448
|
"--log-file",
|
|
40299
|
-
|
|
40449
|
+
join27(exportLogs, "engine.log"),
|
|
40300
40450
|
"--path",
|
|
40301
40451
|
project.value.path,
|
|
40302
40452
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -40304,24 +40454,37 @@ class GodotServer {
|
|
|
40304
40454
|
output.absolutePath
|
|
40305
40455
|
];
|
|
40306
40456
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
40307
|
-
const writtenAt = () =>
|
|
40308
|
-
const
|
|
40309
|
-
const
|
|
40310
|
-
|
|
40311
|
-
|
|
40312
|
-
|
|
40313
|
-
|
|
40314
|
-
|
|
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;
|
|
40315
40475
|
try {
|
|
40316
|
-
|
|
40476
|
+
const first = await exportOnce();
|
|
40477
|
+
docCacheNote = first.crash;
|
|
40478
|
+
run = first.crash === null ? first : await exportOnce();
|
|
40317
40479
|
} finally {
|
|
40318
40480
|
discard(exportLogs);
|
|
40319
40481
|
}
|
|
40482
|
+
const { ending, before, listedBefore } = run;
|
|
40320
40483
|
if (ending.exitSignal === null && ending.exitCode === null) {
|
|
40321
40484
|
return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
|
|
40322
40485
|
}
|
|
40323
|
-
const beside = writtenBeside(listedBefore, filesIn(folder),
|
|
40324
|
-
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);
|
|
40325
40488
|
}
|
|
40326
40489
|
reportTestProgress(log) {
|
|
40327
40490
|
const progress = callProgress();
|
|
@@ -40374,10 +40537,10 @@ class GodotServer {
|
|
|
40374
40537
|
if (movie !== null) {
|
|
40375
40538
|
return this.createErrorResponse(movie);
|
|
40376
40539
|
}
|
|
40377
|
-
const missing = paths.value.filter((path) => !
|
|
40540
|
+
const missing = paths.value.filter((path) => !existsSync21(join27(project.value.path, path.slice("res://".length))));
|
|
40378
40541
|
const asked = paths.value.filter((path) => !missing.includes(path));
|
|
40379
40542
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
40380
|
-
const noRunner = !
|
|
40543
|
+
const noRunner = !existsSync21(join27(project.value.path, runner));
|
|
40381
40544
|
if (asked.length === 0) {
|
|
40382
40545
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
40383
40546
|
const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
|
|
@@ -40468,21 +40631,21 @@ class GodotServer {
|
|
|
40468
40631
|
await untilGone([run.process.pid]);
|
|
40469
40632
|
}
|
|
40470
40633
|
await run.settled;
|
|
40471
|
-
const reportsDir =
|
|
40634
|
+
const reportsDir = join27(project.value.path, ".godot", ...ours.split("/"));
|
|
40472
40635
|
let report = null;
|
|
40473
40636
|
let reportProblem = null;
|
|
40474
40637
|
try {
|
|
40475
|
-
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))) : [];
|
|
40476
40639
|
const newest = written.at(-1);
|
|
40477
40640
|
if (newest !== undefined) {
|
|
40478
|
-
report = parseJUnit(readFileSync24(
|
|
40641
|
+
report = parseJUnit(readFileSync24(join27(reportsDir, newest, "results.xml"), "utf8"));
|
|
40479
40642
|
}
|
|
40480
40643
|
} catch (error) {
|
|
40481
40644
|
reportProblem = errorMessage(error);
|
|
40482
40645
|
} finally {
|
|
40483
40646
|
discard(reportsDir, userData);
|
|
40484
40647
|
try {
|
|
40485
|
-
rmdirSync2(
|
|
40648
|
+
rmdirSync2(dirname19(reportsDir));
|
|
40486
40649
|
} catch {}
|
|
40487
40650
|
}
|
|
40488
40651
|
const engineEntries = foldedRepeats(run.log.select({
|
|
@@ -40514,7 +40677,7 @@ class GodotServer {
|
|
|
40514
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: [] };
|
|
40515
40678
|
const scriptErrors = [...declaring.origins, ...printedErrors];
|
|
40516
40679
|
const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
|
|
40517
|
-
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);
|
|
40518
40681
|
const nothingAt = missing.join(" and ");
|
|
40519
40682
|
const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
|
|
40520
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] ?? "")}`;
|
|
@@ -40841,7 +41004,7 @@ class GodotServer {
|
|
|
40841
41004
|
const cached = cachedClasses(projectPath);
|
|
40842
41005
|
const sourceOf = (resourcePath) => {
|
|
40843
41006
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40844
|
-
if (!contained.ok || !
|
|
41007
|
+
if (!contained.ok || !existsSync21(contained.absolutePath)) {
|
|
40845
41008
|
return null;
|
|
40846
41009
|
}
|
|
40847
41010
|
try {
|
|
@@ -40935,7 +41098,7 @@ class GodotServer {
|
|
|
40935
41098
|
}
|
|
40936
41099
|
sourceOfClass(projectPath, resourcePath) {
|
|
40937
41100
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
40938
|
-
if (!contained.ok || !
|
|
41101
|
+
if (!contained.ok || !existsSync21(contained.absolutePath)) {
|
|
40939
41102
|
return null;
|
|
40940
41103
|
}
|
|
40941
41104
|
try {
|
|
@@ -41097,7 +41260,7 @@ class GodotServer {
|
|
|
41097
41260
|
...Array.isArray(stack) ? {
|
|
41098
41261
|
stack: stack.map((frame) => ({
|
|
41099
41262
|
...frame,
|
|
41100
|
-
...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)) } : {}
|
|
41101
41264
|
}))
|
|
41102
41265
|
} : {}
|
|
41103
41266
|
});
|
|
@@ -41376,7 +41539,7 @@ class GodotServer {
|
|
|
41376
41539
|
return null;
|
|
41377
41540
|
}
|
|
41378
41541
|
try {
|
|
41379
|
-
return settingKeys(readFileSync24(
|
|
41542
|
+
return settingKeys(readFileSync24(join27(projectPath, "project.godot"), "utf8"));
|
|
41380
41543
|
} catch {
|
|
41381
41544
|
return null;
|
|
41382
41545
|
}
|
|
@@ -41813,7 +41976,7 @@ class GodotServer {
|
|
|
41813
41976
|
}
|
|
41814
41977
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
41815
41978
|
const budgetMs = budget.ms;
|
|
41816
|
-
if (!
|
|
41979
|
+
if (!existsSync21(join27(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
41817
41980
|
return runtimeVerdict(null, {
|
|
41818
41981
|
addon: false,
|
|
41819
41982
|
budgetMs,
|
|
@@ -41899,7 +42062,7 @@ class GodotServer {
|
|
|
41899
42062
|
}
|
|
41900
42063
|
let announcedAt;
|
|
41901
42064
|
try {
|
|
41902
|
-
announcedAt =
|
|
42065
|
+
announcedAt = statSync13(endpoint.file).mtimeMs;
|
|
41903
42066
|
} catch {
|
|
41904
42067
|
return;
|
|
41905
42068
|
}
|
|
@@ -42789,9 +42952,13 @@ class GodotServer {
|
|
|
42789
42952
|
if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
|
|
42790
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"]);
|
|
42791
42954
|
}
|
|
42955
|
+
const asked = {
|
|
42956
|
+
mark: readBoolean(args, "mark") === true,
|
|
42957
|
+
since: readString(args, "since") === "mark"
|
|
42958
|
+
};
|
|
42792
42959
|
const going = await this.runStillGoing(run);
|
|
42793
42960
|
const projectPath = run.projectPath;
|
|
42794
|
-
const projectGone = projectPath !== null && !
|
|
42961
|
+
const projectGone = projectPath !== null && !existsSync21(join27(projectPath, "project.godot"));
|
|
42795
42962
|
if (projectGone && callersName !== undefined) {
|
|
42796
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"]);
|
|
42797
42964
|
}
|
|
@@ -42801,7 +42968,7 @@ class GodotServer {
|
|
|
42801
42968
|
...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
|
|
42802
42969
|
};
|
|
42803
42970
|
if (run.throughEditor) {
|
|
42804
|
-
return await this.profileOfThePlayedGame(
|
|
42971
|
+
return await this.profileOfThePlayedGame(run, going, fromProject, limit, asked);
|
|
42805
42972
|
}
|
|
42806
42973
|
const path = run.profile;
|
|
42807
42974
|
if (path === undefined) {
|
|
@@ -42814,15 +42981,16 @@ class GodotServer {
|
|
|
42814
42981
|
]);
|
|
42815
42982
|
}
|
|
42816
42983
|
const totals = file.connected ? file : null;
|
|
42817
|
-
return this.
|
|
42984
|
+
return this.profileReply(run, {
|
|
42818
42985
|
through: "gdharness",
|
|
42819
42986
|
running: going,
|
|
42820
42987
|
totals,
|
|
42821
42988
|
coveredMs: totals?.lastFrameAt == null ? 0 : totals.lastFrameAt - totals.startedAt,
|
|
42822
42989
|
...fromProject
|
|
42823
|
-
}, limit)
|
|
42990
|
+
}, limit, asked);
|
|
42824
42991
|
}
|
|
42825
|
-
async profileOfThePlayedGame(
|
|
42992
|
+
async profileOfThePlayedGame(run, going, fromProject, limit, asked) {
|
|
42993
|
+
const { projectPath } = run;
|
|
42826
42994
|
const read = await this.handleViaBridge("profile_read", {});
|
|
42827
42995
|
if (read.isError === true) {
|
|
42828
42996
|
return read;
|
|
@@ -42844,12 +43012,13 @@ class GodotServer {
|
|
|
42844
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"]);
|
|
42845
43013
|
}
|
|
42846
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." : "";
|
|
42847
43016
|
return this.jsonTextResponse({
|
|
42848
43017
|
through: "editor",
|
|
42849
43018
|
running: true,
|
|
42850
43019
|
started: true,
|
|
42851
43020
|
functions: [],
|
|
42852
|
-
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}`
|
|
42853
43022
|
});
|
|
42854
43023
|
}
|
|
42855
43024
|
const functions = asParams(held["functions"]);
|
|
@@ -42872,13 +43041,70 @@ class GodotServer {
|
|
|
42872
43041
|
];
|
|
42873
43042
|
}))
|
|
42874
43043
|
} : null;
|
|
42875
|
-
return this.
|
|
43044
|
+
return this.profileReply(run, {
|
|
42876
43045
|
through: "editor",
|
|
42877
43046
|
running: going,
|
|
42878
43047
|
totals,
|
|
42879
43048
|
coveredMs: readNonNegativeNumber(held, "coveredMs") ?? 0,
|
|
42880
43049
|
...fromProject
|
|
42881
|
-
}, 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
|
+
});
|
|
42882
43108
|
}
|
|
42883
43109
|
async firstProfiledFrame() {
|
|
42884
43110
|
const deadline = Date.now() + PROFILER_ON_MS;
|
|
@@ -43079,7 +43305,7 @@ class GodotServer {
|
|
|
43079
43305
|
notes.push(endedWithoutACode({
|
|
43080
43306
|
throughEditor: run.throughEditor,
|
|
43081
43307
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
43082
|
-
couldAnnounce: run.projectPath !== null &&
|
|
43308
|
+
couldAnnounce: run.projectPath !== null && existsSync21(join27(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
43083
43309
|
endedHere: typeof run.endedHere === "string",
|
|
43084
43310
|
pickedUp: run.pickedUp === true
|
|
43085
43311
|
}));
|
|
@@ -43709,7 +43935,7 @@ class GodotServer {
|
|
|
43709
43935
|
if (!scriptPath.ok) {
|
|
43710
43936
|
return this.createErrorResponse(scriptPath.reason);
|
|
43711
43937
|
}
|
|
43712
|
-
if (!
|
|
43938
|
+
if (!existsSync21(scriptPath.absolutePath)) {
|
|
43713
43939
|
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
43714
43940
|
}
|
|
43715
43941
|
const asked = readNonEmptyString(args, "newScriptPath");
|
|
@@ -44291,7 +44517,7 @@ class GodotServer {
|
|
|
44291
44517
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
44292
44518
|
const run = await this.currentRun();
|
|
44293
44519
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
44294
|
-
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)) {
|
|
44295
44521
|
return null;
|
|
44296
44522
|
}
|
|
44297
44523
|
const already = runtimesAnnounced().running;
|
|
@@ -44300,7 +44526,7 @@ class GodotServer {
|
|
|
44300
44526
|
return !run.announcedBefore.has(one.pid);
|
|
44301
44527
|
}
|
|
44302
44528
|
try {
|
|
44303
|
-
return
|
|
44529
|
+
return statSync13(one.file).mtimeMs >= run.startedAt;
|
|
44304
44530
|
} catch {
|
|
44305
44531
|
return false;
|
|
44306
44532
|
}
|
|
@@ -44381,7 +44607,7 @@ class GodotServer {
|
|
|
44381
44607
|
}
|
|
44382
44608
|
}
|
|
44383
44609
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
44384
|
-
const screenshotPath = screenshotDir ?
|
|
44610
|
+
const screenshotPath = screenshotDir ? join27(screenshotDir, "capture.png") : null;
|
|
44385
44611
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
44386
44612
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
44387
44613
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|