gdharness 1.3.45 → 1.3.46
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 +291 -191
- package/build/godot/addons/gdharness_editor/animation_reads.gd +8 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +190 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +4 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +89 -3
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +61 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +21 -2
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +18 -5
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +22 -3
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +7 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +9 -0
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +29 -2
- package/build/godot/operations/animation_reads.gd +8 -0
- package/build/godot/operations/editor_tools.gd +2 -0
- package/build/godot/operations/patterns.gd +27 -2
- package/build/godot/operations/project_diagnostics.gd +2 -1
- package/build/godot/operations/project_validation.gd +4 -2
- package/build/keeper.js +3 -2
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -68,6 +68,124 @@ var init_errors = __esm(() => {
|
|
|
68
68
|
};
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
// src/paths.ts
|
|
72
|
+
import { realpathSync, statSync } from "node:fs";
|
|
73
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
74
|
+
function realAsFarAsItExists(path) {
|
|
75
|
+
const missing = [];
|
|
76
|
+
let at = path;
|
|
77
|
+
for (;; ) {
|
|
78
|
+
try {
|
|
79
|
+
return join(realpathSync.native(at), ...missing);
|
|
80
|
+
} catch {
|
|
81
|
+
const parent = dirname(at);
|
|
82
|
+
if (parent === at) {
|
|
83
|
+
return path;
|
|
84
|
+
}
|
|
85
|
+
missing.unshift(basename(at));
|
|
86
|
+
at = parent;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function isSameDirectory(onePath, otherPath) {
|
|
91
|
+
return relative(realAsFarAsItExists(resolve(onePath)), realAsFarAsItExists(resolve(otherPath))) === "";
|
|
92
|
+
}
|
|
93
|
+
function isWithinRoot(rootPath, candidatePath) {
|
|
94
|
+
const step = relative(resolve(rootPath), resolve(candidatePath));
|
|
95
|
+
return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
|
|
96
|
+
}
|
|
97
|
+
function keptInside(given) {
|
|
98
|
+
const at = given.indexOf("::");
|
|
99
|
+
const inner = at === -1 ? "" : given.slice(at + 2);
|
|
100
|
+
return KEPT_ID.test(inner) ? { file: given.slice(0, at), inner } : { file: given, inner: undefined };
|
|
101
|
+
}
|
|
102
|
+
function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
103
|
+
const trimmed = candidatePath.trim();
|
|
104
|
+
if (trimmed === "") {
|
|
105
|
+
return { ok: false, reason: "Path is empty." };
|
|
106
|
+
}
|
|
107
|
+
if (trimmed.includes("\x00")) {
|
|
108
|
+
return { ok: false, reason: "Path contains a null byte." };
|
|
109
|
+
}
|
|
110
|
+
const root = resolve(projectPath);
|
|
111
|
+
const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : isAbsolute(trimmed) && isWithinRoot(root, trimmed) ? relative(root, resolve(trimmed)).split(sep).join("/") : trimmed;
|
|
112
|
+
if (withoutScheme.startsWith("uid://")) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
reason: `Path '${trimmed}' is a UID, and a file is judged here by where it is on disk, which a UID does not say. Give the file's res:// path.`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const scheme = OTHER_SCHEME.exec(withoutScheme);
|
|
119
|
+
if (scheme) {
|
|
120
|
+
const elsewhere = scheme[0].toLowerCase() === "user://" ? " A game's user:// is a directory of its own outside the project: a running game's is under userDir in editor_status runtimes and in editor_run start's runtime, where a file reader of your own reads it." : "";
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.${elsewhere}`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
|
|
127
|
+
const tail = withoutScheme.split(/[\\/]+/).filter((step) => step !== "" && !step.endsWith(":"));
|
|
128
|
+
const besideIt = isAbsolute(trimmed) && isWithinRoot(realAsFarAsItExists(dirname(resolve(trimmed))), realAsFarAsItExists(root));
|
|
129
|
+
const example = tail.slice(besideIt ? -1 : -2).join("/") || "scenes/main.tscn";
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as '${example}'.`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const kept = keptInside(trimmed);
|
|
136
|
+
if (kept.inner !== undefined) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
reason: `Path '${trimmed}' names the resource ${kept.inner} kept inside ${kept.file}, not a file. resource_edit read reads one by that path; this argument takes a file.`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (withoutScheme.includes(":")) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const absolutePath = resolve(root, withoutScheme);
|
|
149
|
+
if (!isWithinRoot(root, absolutePath)) {
|
|
150
|
+
return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
|
|
151
|
+
}
|
|
152
|
+
const relativePath = relative(root, absolutePath).split(sep).join("/");
|
|
153
|
+
if (relativePath === "" && options.root !== true) {
|
|
154
|
+
return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
|
|
155
|
+
}
|
|
156
|
+
return { ok: true, absolutePath, relativePath };
|
|
157
|
+
}
|
|
158
|
+
function exportedBuild(projectPath, candidatePath) {
|
|
159
|
+
const found = resolveWithinProject(projectPath, candidatePath);
|
|
160
|
+
if (!found.ok) {
|
|
161
|
+
return found;
|
|
162
|
+
}
|
|
163
|
+
let real;
|
|
164
|
+
try {
|
|
165
|
+
real = realpathSync.native(found.absolutePath);
|
|
166
|
+
} catch {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
reason: `There is no ${found.relativePath} in the project to start: export the build first, with project_export run.`
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (!isWithinRoot(realAsFarAsItExists(projectPath), real)) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
reason: `${found.relativePath} leads outside the project, to ${real}, so it is not started.`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (!statSync(real).isFile()) {
|
|
179
|
+
return { ok: false, reason: `${found.relativePath} is not a file, so there is nothing to start.` };
|
|
180
|
+
}
|
|
181
|
+
return { ok: true, absolutePath: real, relativePath: found.relativePath };
|
|
182
|
+
}
|
|
183
|
+
var RESOURCE_SCHEME = "res://", OTHER_SCHEME, KEPT_ID;
|
|
184
|
+
var init_paths = __esm(() => {
|
|
185
|
+
OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
|
|
186
|
+
KEPT_ID = /^[A-Za-z0-9_]+$/;
|
|
187
|
+
});
|
|
188
|
+
|
|
71
189
|
// src/spawn.ts
|
|
72
190
|
import {
|
|
73
191
|
execFile,
|
|
@@ -243,7 +361,7 @@ var init_windows_ask = __esm(() => {
|
|
|
243
361
|
|
|
244
362
|
// src/process-children.ts
|
|
245
363
|
import { readFileSync } from "node:fs";
|
|
246
|
-
import { basename } from "node:path";
|
|
364
|
+
import { basename as basename2 } from "node:path";
|
|
247
365
|
async function startTimesOf(pids) {
|
|
248
366
|
const began = new Map;
|
|
249
367
|
if (pids.length === 0) {
|
|
@@ -345,7 +463,7 @@ function listingFailure(error) {
|
|
|
345
463
|
const failed = error;
|
|
346
464
|
const how = failed.killed === true ? `was killed after the ${ASK_TIMEOUT_MS}ms budget${failed.signal ? ` (${failed.signal})` : ""}` : failed.signal ? `was ended by ${failed.signal}` : failed.code !== undefined && failed.code !== null ? `exited with ${failed.code}` : "failed";
|
|
347
465
|
const said = typeof failed.stderr === "string" ? failed.stderr.trim() : "";
|
|
348
|
-
const asked =
|
|
466
|
+
const asked = basename2((failed.cmd ?? "").split(" ")[0] ?? "") || "the listing";
|
|
349
467
|
return `${asked} ${how}${said === "" ? ", with nothing on stderr" : `; stderr: ${said.slice(0, 400)}`}`;
|
|
350
468
|
}
|
|
351
469
|
function whyTheProcessTreeFailed() {
|
|
@@ -440,11 +558,21 @@ function ancestorsIn(tree, pid, root) {
|
|
|
440
558
|
}
|
|
441
559
|
return chain;
|
|
442
560
|
}
|
|
561
|
+
function editorsOfProjectIn(tree, projectPath) {
|
|
562
|
+
const found = [];
|
|
563
|
+
for (const [pid, listed] of tree) {
|
|
564
|
+
const read = readCommandLine(listed.command);
|
|
565
|
+
if (read.editor && read.oneShot === null && read.projectPath !== null && /godot/i.test(read.executable) && !CONSOLE_WRAPPER.test(read.executable) && isSameDirectory(read.projectPath, projectPath)) {
|
|
566
|
+
found.push(pid);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return found.sort((a, b) => a - b);
|
|
570
|
+
}
|
|
443
571
|
function readCommandLine(command) {
|
|
444
572
|
const all = commandWords(command);
|
|
445
573
|
const separator = all.findIndex((word) => word === "--" || word === "++");
|
|
446
574
|
const words = separator === -1 ? all : all.slice(0, separator);
|
|
447
|
-
const executable = words[0] === undefined ? "" :
|
|
575
|
+
const executable = words[0] === undefined ? "" : basename2(words[0].replaceAll("\\", "/"));
|
|
448
576
|
const at = words.indexOf("--path");
|
|
449
577
|
let projectPath = null;
|
|
450
578
|
if (at !== -1 && at + 1 < words.length) {
|
|
@@ -507,8 +635,9 @@ async function askPosix() {
|
|
|
507
635
|
});
|
|
508
636
|
return stdout;
|
|
509
637
|
}
|
|
510
|
-
var ticksAnswered, ASK_TIMEOUT_MS = 15000, lastListingFailure, ONE_SHOT_OPTIONS;
|
|
638
|
+
var ticksAnswered, ASK_TIMEOUT_MS = 15000, lastListingFailure, ONE_SHOT_OPTIONS, CONSOLE_WRAPPER;
|
|
511
639
|
var init_process_children = __esm(() => {
|
|
640
|
+
init_paths();
|
|
512
641
|
init_spawn();
|
|
513
642
|
init_windows_ask();
|
|
514
643
|
ONE_SHOT_OPTIONS = [
|
|
@@ -532,13 +661,14 @@ var init_process_children = __esm(() => {
|
|
|
532
661
|
"--convert-3to4",
|
|
533
662
|
"--validate-conversion-3to4"
|
|
534
663
|
];
|
|
664
|
+
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
535
665
|
});
|
|
536
666
|
|
|
537
667
|
// src/bridge-announce.ts
|
|
538
668
|
import { mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "node:fs";
|
|
539
|
-
import { dirname, join } from "node:path";
|
|
669
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
540
670
|
function announcementPath(projectPath) {
|
|
541
|
-
return
|
|
671
|
+
return join2(projectPath, ".godot", "gdharness-bridge.json");
|
|
542
672
|
}
|
|
543
673
|
function readAnnouncement(path) {
|
|
544
674
|
try {
|
|
@@ -585,7 +715,7 @@ function announcementIsOurs(path) {
|
|
|
585
715
|
function announceBridge(projectPath, bridge) {
|
|
586
716
|
const path = announcementPath(projectPath);
|
|
587
717
|
try {
|
|
588
|
-
mkdirSync(
|
|
718
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
589
719
|
const announcement = {
|
|
590
720
|
protocol: BRIDGE_ANNOUNCE_PROTOCOL,
|
|
591
721
|
host: bridge.host,
|
|
@@ -652,15 +782,15 @@ import {
|
|
|
652
782
|
openSync,
|
|
653
783
|
readFileSync as readFileSync3,
|
|
654
784
|
rmSync as rmSync2,
|
|
655
|
-
statSync,
|
|
785
|
+
statSync as statSync2,
|
|
656
786
|
writeFileSync as writeFileSync2,
|
|
657
787
|
writeSync
|
|
658
788
|
} from "node:fs";
|
|
659
789
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
660
|
-
import { join as
|
|
790
|
+
import { join as join3 } from "node:path";
|
|
661
791
|
import { setTimeout as delay } from "node:timers/promises";
|
|
662
792
|
function helperAssembly(directory = tmpdir2()) {
|
|
663
|
-
return
|
|
793
|
+
return join3(directory, `gdharness-desktop-${DIGEST}.dll`);
|
|
664
794
|
}
|
|
665
795
|
async function ensureHelperCompiled(directory = tmpdir2(), budgetMs = HELPER_COMPILE_MS, wait = true) {
|
|
666
796
|
const assembly = helperAssembly(directory);
|
|
@@ -686,7 +816,7 @@ async function ensureHelperCompiled(directory = tmpdir2(), budgetMs = HELPER_COM
|
|
|
686
816
|
}
|
|
687
817
|
try {
|
|
688
818
|
const holder = Number(readFileSync3(lock, "utf8").trim());
|
|
689
|
-
const old = Date.now() -
|
|
819
|
+
const old = Date.now() - statSync2(lock).mtimeMs > budgetMs;
|
|
690
820
|
if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
|
|
691
821
|
rmSync2(lock, { force: true });
|
|
692
822
|
continue;
|
|
@@ -700,8 +830,8 @@ async function ensureHelperCompiled(directory = tmpdir2(), budgetMs = HELPER_COM
|
|
|
700
830
|
return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
|
|
701
831
|
}
|
|
702
832
|
async function compileHelper(directory, assembly, budgetMs) {
|
|
703
|
-
const scratch = mkdtempSync(
|
|
704
|
-
const source =
|
|
833
|
+
const scratch = mkdtempSync(join3(directory, "gdharness-desktop-source-"));
|
|
834
|
+
const source = join3(scratch, "helper.cs");
|
|
705
835
|
writeFileSync2(source, SOURCE, "utf8");
|
|
706
836
|
const literal = (path) => `'${path.replaceAll("'", "''")}'`;
|
|
707
837
|
const script = [
|
|
@@ -8135,124 +8265,6 @@ var init_config_formats = __esm(() => {
|
|
|
8135
8265
|
init_dist();
|
|
8136
8266
|
});
|
|
8137
8267
|
|
|
8138
|
-
// src/paths.ts
|
|
8139
|
-
import { realpathSync, statSync as statSync2 } from "node:fs";
|
|
8140
|
-
import { basename as basename2, dirname as dirname2, isAbsolute, join as join3, relative, resolve, sep } from "node:path";
|
|
8141
|
-
function realAsFarAsItExists(path) {
|
|
8142
|
-
const missing = [];
|
|
8143
|
-
let at = path;
|
|
8144
|
-
for (;; ) {
|
|
8145
|
-
try {
|
|
8146
|
-
return join3(realpathSync.native(at), ...missing);
|
|
8147
|
-
} catch {
|
|
8148
|
-
const parent = dirname2(at);
|
|
8149
|
-
if (parent === at) {
|
|
8150
|
-
return path;
|
|
8151
|
-
}
|
|
8152
|
-
missing.unshift(basename2(at));
|
|
8153
|
-
at = parent;
|
|
8154
|
-
}
|
|
8155
|
-
}
|
|
8156
|
-
}
|
|
8157
|
-
function isSameDirectory(onePath, otherPath) {
|
|
8158
|
-
return relative(realAsFarAsItExists(resolve(onePath)), realAsFarAsItExists(resolve(otherPath))) === "";
|
|
8159
|
-
}
|
|
8160
|
-
function isWithinRoot(rootPath, candidatePath) {
|
|
8161
|
-
const step = relative(resolve(rootPath), resolve(candidatePath));
|
|
8162
|
-
return step !== ".." && !step.startsWith(`..${sep}`) && !isAbsolute(step);
|
|
8163
|
-
}
|
|
8164
|
-
function keptInside(given) {
|
|
8165
|
-
const at = given.indexOf("::");
|
|
8166
|
-
const inner = at === -1 ? "" : given.slice(at + 2);
|
|
8167
|
-
return KEPT_ID.test(inner) ? { file: given.slice(0, at), inner } : { file: given, inner: undefined };
|
|
8168
|
-
}
|
|
8169
|
-
function resolveWithinProject(projectPath, candidatePath, options = {}) {
|
|
8170
|
-
const trimmed = candidatePath.trim();
|
|
8171
|
-
if (trimmed === "") {
|
|
8172
|
-
return { ok: false, reason: "Path is empty." };
|
|
8173
|
-
}
|
|
8174
|
-
if (trimmed.includes("\x00")) {
|
|
8175
|
-
return { ok: false, reason: "Path contains a null byte." };
|
|
8176
|
-
}
|
|
8177
|
-
const root = resolve(projectPath);
|
|
8178
|
-
const withoutScheme = trimmed.startsWith(RESOURCE_SCHEME) ? trimmed.slice(RESOURCE_SCHEME.length) : isAbsolute(trimmed) && isWithinRoot(root, trimmed) ? relative(root, resolve(trimmed)).split(sep).join("/") : trimmed;
|
|
8179
|
-
if (withoutScheme.startsWith("uid://")) {
|
|
8180
|
-
return {
|
|
8181
|
-
ok: false,
|
|
8182
|
-
reason: `Path '${trimmed}' is a UID, and a file is judged here by where it is on disk, which a UID does not say. Give the file's res:// path.`
|
|
8183
|
-
};
|
|
8184
|
-
}
|
|
8185
|
-
const scheme = OTHER_SCHEME.exec(withoutScheme);
|
|
8186
|
-
if (scheme) {
|
|
8187
|
-
const elsewhere = scheme[0].toLowerCase() === "user://" ? " A game's user:// is a directory of its own outside the project: a running game's is under userDir in editor_status runtimes and in editor_run start's runtime, where a file reader of your own reads it." : "";
|
|
8188
|
-
return {
|
|
8189
|
-
ok: false,
|
|
8190
|
-
reason: `Path '${trimmed}' uses the '${scheme[0]}' scheme, which names somewhere other than this project.${elsewhere}`
|
|
8191
|
-
};
|
|
8192
|
-
}
|
|
8193
|
-
if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
|
|
8194
|
-
const tail = withoutScheme.split(/[\\/]+/).filter((step) => step !== "" && !step.endsWith(":"));
|
|
8195
|
-
const besideIt = isAbsolute(trimmed) && isWithinRoot(realAsFarAsItExists(dirname2(resolve(trimmed))), realAsFarAsItExists(root));
|
|
8196
|
-
const example = tail.slice(besideIt ? -1 : -2).join("/") || "scenes/main.tscn";
|
|
8197
|
-
return {
|
|
8198
|
-
ok: false,
|
|
8199
|
-
reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as '${example}'.`
|
|
8200
|
-
};
|
|
8201
|
-
}
|
|
8202
|
-
const kept = keptInside(trimmed);
|
|
8203
|
-
if (kept.inner !== undefined) {
|
|
8204
|
-
return {
|
|
8205
|
-
ok: false,
|
|
8206
|
-
reason: `Path '${trimmed}' names the resource ${kept.inner} kept inside ${kept.file}, not a file. resource_edit read reads one by that path; this argument takes a file.`
|
|
8207
|
-
};
|
|
8208
|
-
}
|
|
8209
|
-
if (withoutScheme.includes(":")) {
|
|
8210
|
-
return {
|
|
8211
|
-
ok: false,
|
|
8212
|
-
reason: `Path '${trimmed}' contains ':', which no file inside a project is named with.`
|
|
8213
|
-
};
|
|
8214
|
-
}
|
|
8215
|
-
const absolutePath = resolve(root, withoutScheme);
|
|
8216
|
-
if (!isWithinRoot(root, absolutePath)) {
|
|
8217
|
-
return { ok: false, reason: `Path '${trimmed}' resolves outside the project directory.` };
|
|
8218
|
-
}
|
|
8219
|
-
const relativePath = relative(root, absolutePath).split(sep).join("/");
|
|
8220
|
-
if (relativePath === "" && options.root !== true) {
|
|
8221
|
-
return { ok: false, reason: `Path '${trimmed}' is the project directory itself, not a file in it.` };
|
|
8222
|
-
}
|
|
8223
|
-
return { ok: true, absolutePath, relativePath };
|
|
8224
|
-
}
|
|
8225
|
-
function exportedBuild(projectPath, candidatePath) {
|
|
8226
|
-
const found = resolveWithinProject(projectPath, candidatePath);
|
|
8227
|
-
if (!found.ok) {
|
|
8228
|
-
return found;
|
|
8229
|
-
}
|
|
8230
|
-
let real;
|
|
8231
|
-
try {
|
|
8232
|
-
real = realpathSync.native(found.absolutePath);
|
|
8233
|
-
} catch {
|
|
8234
|
-
return {
|
|
8235
|
-
ok: false,
|
|
8236
|
-
reason: `There is no ${found.relativePath} in the project to start: export the build first, with project_export run.`
|
|
8237
|
-
};
|
|
8238
|
-
}
|
|
8239
|
-
if (!isWithinRoot(realAsFarAsItExists(projectPath), real)) {
|
|
8240
|
-
return {
|
|
8241
|
-
ok: false,
|
|
8242
|
-
reason: `${found.relativePath} leads outside the project, to ${real}, so it is not started.`
|
|
8243
|
-
};
|
|
8244
|
-
}
|
|
8245
|
-
if (!statSync2(real).isFile()) {
|
|
8246
|
-
return { ok: false, reason: `${found.relativePath} is not a file, so there is nothing to start.` };
|
|
8247
|
-
}
|
|
8248
|
-
return { ok: true, absolutePath: real, relativePath: found.relativePath };
|
|
8249
|
-
}
|
|
8250
|
-
var RESOURCE_SCHEME = "res://", OTHER_SCHEME, KEPT_ID;
|
|
8251
|
-
var init_paths = __esm(() => {
|
|
8252
|
-
OTHER_SCHEME = /^[a-zA-Z][a-zA-Z\d+.-]+:\/\//;
|
|
8253
|
-
KEPT_ID = /^[A-Za-z0-9_]+$/;
|
|
8254
|
-
});
|
|
8255
|
-
|
|
8256
8268
|
// src/runner.ts
|
|
8257
8269
|
import { existsSync as existsSync2 } from "node:fs";
|
|
8258
8270
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
@@ -9905,6 +9917,29 @@ function cachedClasses(projectPath) {
|
|
|
9905
9917
|
}
|
|
9906
9918
|
return listed;
|
|
9907
9919
|
}
|
|
9920
|
+
function cachedEntries(projectPath) {
|
|
9921
|
+
const text = readWhole(join6(projectPath, ".godot", "global_script_class_cache.cfg"));
|
|
9922
|
+
if (text === null) {
|
|
9923
|
+
return null;
|
|
9924
|
+
}
|
|
9925
|
+
const listed = new Map;
|
|
9926
|
+
for (const entry of text.matchAll(/\{[^{}]*\}/g)) {
|
|
9927
|
+
const named = /"class":\s*&"([^"]+)"/.exec(entry[0])?.[1];
|
|
9928
|
+
if (named !== undefined) {
|
|
9929
|
+
listed.set(named, entry[0]);
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
return listed;
|
|
9933
|
+
}
|
|
9934
|
+
function cacheChangesBetween(before, after) {
|
|
9935
|
+
if (before === null || after === null) {
|
|
9936
|
+
return {};
|
|
9937
|
+
}
|
|
9938
|
+
const added = [...after.keys()].filter((name) => !before.has(name)).sort();
|
|
9939
|
+
const removed = [...before.keys()].filter((name) => !after.has(name)).sort();
|
|
9940
|
+
const changed = [...after.keys()].filter((name) => before.has(name) && before.get(name) !== after.get(name)).sort();
|
|
9941
|
+
return { added, removed, changed };
|
|
9942
|
+
}
|
|
9908
9943
|
function addonOf(path) {
|
|
9909
9944
|
return /^res:\/\/addons\/([^/]+)\//.exec(path)?.[1];
|
|
9910
9945
|
}
|
|
@@ -9921,6 +9956,20 @@ function withAddonPathsCounted(paths) {
|
|
|
9921
9956
|
}
|
|
9922
9957
|
return { own, inAddons };
|
|
9923
9958
|
}
|
|
9959
|
+
function classesWithAddonsCounted(classes, listed) {
|
|
9960
|
+
const kept = [];
|
|
9961
|
+
const inAddons = {};
|
|
9962
|
+
for (const entry of classes) {
|
|
9963
|
+
const path = entry !== null && typeof entry === "object" ? entry["path"] : "";
|
|
9964
|
+
const addon = addonOf(typeof path === "string" ? path : "");
|
|
9965
|
+
if (addon === undefined || listed.has(addon)) {
|
|
9966
|
+
kept.push(entry);
|
|
9967
|
+
} else {
|
|
9968
|
+
inAddons[addon] = (inAddons[addon] ?? 0) + 1;
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
return { classes: kept, inAddons };
|
|
9972
|
+
}
|
|
9924
9973
|
function withAddonClassesCounted(rebuilt, listed) {
|
|
9925
9974
|
const counted = { ...rebuilt };
|
|
9926
9975
|
for (const key of ["added", "changed"]) {
|
|
@@ -14231,7 +14280,8 @@ async function operationOutcome(engine, operation, params, projectPath) {
|
|
|
14231
14280
|
...afterAnswered
|
|
14232
14281
|
};
|
|
14233
14282
|
}
|
|
14234
|
-
const
|
|
14283
|
+
const onTheWayOut = answered === -1 ? problems : after;
|
|
14284
|
+
const afterAnswer = ran.exitCode === 0 && ran.failure === null ? undefined : `The operation finished and wrote this answer, and then the engine did not exit cleanly (${howItEnded(ran)}). ${onTheWayOut.length === 0 ? "It printed no error or warning on the way out." : `What it reported on the way out is under ${answered === -1 ? "engine_messages" : "engine_messages_after_answer"}.`}`;
|
|
14235
14285
|
return {
|
|
14236
14286
|
ok: true,
|
|
14237
14287
|
payload: answer.payload,
|
|
@@ -14794,7 +14844,7 @@ var init_ports = __esm(() => {
|
|
|
14794
14844
|
import { existsSync as existsSync16, realpathSync as realpathSync2 } from "node:fs";
|
|
14795
14845
|
import { readFile, realpath } from "node:fs/promises";
|
|
14796
14846
|
import { createConnection } from "node:net";
|
|
14797
|
-
import { dirname as dirname8, join as join20, resolve as resolve6 } from "node:path";
|
|
14847
|
+
import { dirname as dirname8, extname as extname2, join as join20, resolve as resolve6 } from "node:path";
|
|
14798
14848
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
14799
14849
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14800
14850
|
function initializeTimeoutMs() {
|
|
@@ -15485,6 +15535,11 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
15485
15535
|
} catch {
|
|
15486
15536
|
throw new ArgumentRefusal(`Script file does not exist: res://${contained.relativePath}`);
|
|
15487
15537
|
}
|
|
15538
|
+
if (!scriptPath.toLowerCase().endsWith(".gd")) {
|
|
15539
|
+
const said = `res://${contained.relativePath} is not a GDScript file, and the language server reads .gd files only`;
|
|
15540
|
+
const kind = extname2(scriptPath).toLowerCase();
|
|
15541
|
+
throw new ArgumentRefusal(kind === ".tscn" || kind === ".scn" ? `${said}: scene_tree reads a scene's nodes, and with properties the script each carries.` : kind === ".tres" || kind === ".res" ? `${said}: resource_edit read reads a resource.` : `${said}.`);
|
|
15542
|
+
}
|
|
15488
15543
|
if (!isWithinRoot(projectPath, scriptPath)) {
|
|
15489
15544
|
throw new ArgumentRefusal("scriptPath resolves outside the project root boundary.");
|
|
15490
15545
|
}
|
|
@@ -15633,7 +15688,7 @@ var init_lsp_client = __esm(() => {
|
|
|
15633
15688
|
|
|
15634
15689
|
// src/resources.ts
|
|
15635
15690
|
import { readFileSync as readFileSync17, realpathSync as realpathSync3 } from "node:fs";
|
|
15636
|
-
import { extname as
|
|
15691
|
+
import { extname as extname3, resolve as resolve8 } from "node:path";
|
|
15637
15692
|
function ensureProjectPath(getProjectPath) {
|
|
15638
15693
|
const projectPath = getProjectPath();
|
|
15639
15694
|
if (!projectPath) {
|
|
@@ -15692,7 +15747,7 @@ function parseGodotUri(uri) {
|
|
|
15692
15747
|
}
|
|
15693
15748
|
function ensureAllowedExtension(kind, filePath) {
|
|
15694
15749
|
const allowed = EXTENSIONS[kind];
|
|
15695
|
-
if (!allowed.includes(
|
|
15750
|
+
if (!allowed.includes(extname3(filePath).toLowerCase())) {
|
|
15696
15751
|
throw new Refusal(`godot://${kind}/ reads only ${allowed.join(", ")} files.`);
|
|
15697
15752
|
}
|
|
15698
15753
|
}
|
|
@@ -16040,6 +16095,25 @@ function shippedRuntimeDigest() {
|
|
|
16040
16095
|
function installedEditorDigest(projectPath) {
|
|
16041
16096
|
return digestMarker(projectPath, ADDONS[0]);
|
|
16042
16097
|
}
|
|
16098
|
+
function editorUpgradeNote(sameCode, editors) {
|
|
16099
|
+
if (sameCode) {
|
|
16100
|
+
return ` 1. Nothing for the editor: this upgrade left the editor addons the same code as before,
|
|
16101
|
+
` + ` so it gives an open editor nothing to restart for. editor_status says addonIsStale
|
|
16102
|
+
` + " if the editor is running other code.";
|
|
16103
|
+
}
|
|
16104
|
+
if (editors === null) {
|
|
16105
|
+
return ` 1. An editor of this project that is open is still running the addons it loaded at
|
|
16106
|
+
` + ` startup. Restart it with the editor_launch restart tool, which closes and reopens
|
|
16107
|
+
` + " the window.";
|
|
16108
|
+
}
|
|
16109
|
+
if (editors.length === 0) {
|
|
16110
|
+
return ` 1. Nothing for the editor: none of this project is running, and the next one opened loads
|
|
16111
|
+
these addons.`;
|
|
16112
|
+
}
|
|
16113
|
+
const which = editors.length === 1 ? `The open editor (pid ${editors[0]}) is` : `The open editors (pids ${editors.join(", ")}) are`;
|
|
16114
|
+
return ` 1. ${which} still running the addons loaded at startup. Restart with the
|
|
16115
|
+
` + " editor_launch restart tool, which closes and reopens the window.";
|
|
16116
|
+
}
|
|
16043
16117
|
function installedRuntimeDigest(projectPath) {
|
|
16044
16118
|
return digestMarker(projectPath, RUNTIME_ADDON);
|
|
16045
16119
|
}
|
|
@@ -16511,6 +16585,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16511
16585
|
},
|
|
16512
16586
|
nodePath: { type: "string", description: "Target node, relative to the AnimationPlayer root." },
|
|
16513
16587
|
property: { type: "string", description: "For property tracks: the property to animate." },
|
|
16588
|
+
update: {
|
|
16589
|
+
type: "string",
|
|
16590
|
+
enum: ["continuous", "discrete", "capture"],
|
|
16591
|
+
description: "For property tracks: how the value moves between keys. continuous blends toward the next key, so a bool or an int changes halfway to it; discrete holds each key's value until the next; capture blends from the value the property holds when playing starts. Without it a new track is made as the editor makes one: as the RESET animation's track for the same property, otherwise continuous for a float, vector, colour or transform and discrete for anything else. The answer gives the track's update."
|
|
16592
|
+
},
|
|
16514
16593
|
method: {
|
|
16515
16594
|
type: "string",
|
|
16516
16595
|
description: "For method tracks: the method to call. A method track holds every method key for its node, so a key calling another method joins the node's track, with existingTrack true. The answer's methodFound says whether the target node has it; a method it lacks is still added, while a nodePath that reaches no node is refused, since it may be written next, and until it exists each call the track makes fails. A key whose args the method cannot take (too few or too many, or an object where it takes a value or the reverse) is written too, and answers argumentsFit false with misfit saying why. remove_track: only the keys calling this method."
|
|
@@ -16651,7 +16730,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
16651
16730
|
include: {
|
|
16652
16731
|
type: "array",
|
|
16653
16732
|
items: { type: "string", enum: [...PROJECT_INFO_SECTIONS] },
|
|
16654
|
-
description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets (runnable marks the one preset of its platform the editor's one-click deploy uses), the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, each input action a script reads by a name written in the call that neither the project nor the engine defines, with every line reading it, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
|
|
16733
|
+
description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files (those an addon declares counted per addon under inAddons, unless addonClasses names the addon), the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets (runnable marks the one preset of its platform the editor's one-click deploy uses), the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, each input action a script reads by a name written in the call that neither the project nor the engine defines, with every line reading it, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
|
|
16734
|
+
},
|
|
16735
|
+
addonClasses: {
|
|
16736
|
+
type: "array",
|
|
16737
|
+
items: { type: "string" },
|
|
16738
|
+
description: "For classes: the addons, by their folder under res://addons, whose classes are listed by name and script like the project's own, rather than counted under inAddons. Default none: a game with two large addons answered 686 classes, 19 of them its own."
|
|
16655
16739
|
},
|
|
16656
16740
|
preset: { type: "string", description: "For validation: the export preset to validate against." },
|
|
16657
16741
|
detail: {
|
|
@@ -17144,7 +17228,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17144
17228
|
name: "scene_node",
|
|
17145
17229
|
title: "Scene nodes",
|
|
17146
17230
|
description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, paint TileMap cells, or bake a navigation region. 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, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own, and answers each rewrite under retargeted and each path it left under notRetargeted. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A delete rewrites no path, and answers under stillNaming what named the deleted node and now names nothing. 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. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
|
|
17147
|
-
answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}; namesNoNode, each node path written naming no node; sharedWith, for a property written through a resource built into the scene, each other {node, property} holding it, which changed too; setByScript, what a script that is not a tool set as a headless write ran, which the file does not hold; loadsAs, a value given that the file holds and the node's own script changes as the scene loads, as it loads. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
|
|
17231
|
+
answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node; a property typed as a node, or an array or dictionary of nodes, holds the node itself, so the one that held a deleted node holds null there, as the editor saves it, and its entry adds cleared: true. deletedWithIt lists each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn or that the new parent sets, as a container gives a Control its layout_mode, as {was, now}; namesNoNode, each node path written naming no node; sharedWith, for a property written through a resource built into the scene, each other {node, property} holding it, which changed too; setByScript, what a script that is not a tool set as a headless write ran, which the file does not hold; loadsAs, a value given that the file holds and the node's own script changes as the scene loads, as it loads. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
|
|
17148
17232
|
parameters: {
|
|
17149
17233
|
projectPath: PROJECT_PATH,
|
|
17150
17234
|
scenePath: SCENE_PATH,
|
|
@@ -17186,7 +17270,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
17186
17270
|
ops: ["set"],
|
|
17187
17271
|
description: "set: groups to take the node out of. One it is not in is refused, and so is one another scene puts it in (the scene that makes it, instances it or is inherited here), which only that scene can take it out of."
|
|
17188
17272
|
},
|
|
17189
|
-
newName: {
|
|
17273
|
+
newName: {
|
|
17274
|
+
type: "string",
|
|
17275
|
+
ops: ["duplicate"],
|
|
17276
|
+
description: "duplicate: the copy's name. The copy has the nodes under the original and their connections, as the editor's Duplicate makes it: each connection keeps its flags, binds and unbinds, and one between two copied nodes joins the copies."
|
|
17277
|
+
},
|
|
17190
17278
|
newParentPath: {
|
|
17191
17279
|
type: "string",
|
|
17192
17280
|
ops: ["reparent"],
|
|
@@ -17258,7 +17346,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17258
17346
|
{
|
|
17259
17347
|
name: "scene_signal",
|
|
17260
17348
|
title: "Scene signals",
|
|
17261
|
-
description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connection carries binds, the values passed after the signal's own, and unbinds, how many of the signal's own are dropped first, as the editor's connect dialog offers them; each is answered only when there is one. A connect of a pair already connected says alreadyConnected, with flagsBefore, bindsBefore and unbindsBefore when it changed them, and keeps the binds and unbinds it had unless the call gives new ones. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. One whose method cannot take what the signal passes, with its binds and unbinds (too few or too many arguments, an object where the method takes a value or the reverse, or an engine class unrelated to the one it takes), is written too, and answers argumentsFit false with misfit saying why; list gives each connection its methodFound, and misfit when it has one. list marks editable false on a connection a scene this one instances or inherits makes, on its own root as well as inside it, and names that scene under madeIn; only that scene can change it, so a disconnect of one is refused. ${WITHOUT_AN_EDITOR} ${ALSO_SAVED}`,
|
|
17349
|
+
description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connection carries binds, the values passed after the signal's own, and unbinds, how many of the signal's own are dropped first, as the editor's connect dialog offers them; each is answered only when there is one. A connect of a pair already connected says alreadyConnected, with flagsBefore, bindsBefore and unbindsBefore when it changed them, and keeps the binds and unbinds it had unless the call gives new ones. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. That holds only without binds and unbinds: the engine makes a connection that binds or unbinds arguments only to a method its target has, and a scene loads without one aimed elsewhere, so such a connect is refused. A file can hold one already, written by hand or before its method was renamed: the engine loads the scene without it and a save drops it, so every tool that writes a .tscn puts it back as the file held it and says so under unmadeConnections, a write to a binary .scn holding one is refused, and a disconnect takes it out. One whose method cannot take what the signal passes, with its binds and unbinds (too few or too many arguments, an object where the method takes a value or the reverse, or an engine class unrelated to the one it takes), is written too, and answers argumentsFit false with misfit saying why; list gives each connection its methodFound, and misfit when it has one. list marks editable false on a connection a scene this one instances or inherits makes, on its own root as well as inside it, and names that scene under madeIn; only that scene can change it, so a disconnect of one is refused. ${WITHOUT_AN_EDITOR} ${ALSO_SAVED}`,
|
|
17262
17350
|
parameters: {
|
|
17263
17351
|
projectPath: PROJECT_PATH,
|
|
17264
17352
|
scenePath: SCENE_PATH,
|
|
@@ -18065,7 +18153,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18065
18153
|
property: {
|
|
18066
18154
|
type: "string",
|
|
18067
18155
|
ops: ["property", "find"],
|
|
18068
|
-
description: `property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A key holding a colon, a bracket or a quote is written in double quotes, 'stock:"base:dew"', as a refusal listing the keys writes it. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A vector, a colour, a rectangle or a transform is stepped into by its components as GDScript names them, so one axis is one number: "size:x", "global_position:y", "modulate:a", "global_transform:origin:z"; a name the value does not have is refused, naming the ones it has. It also answers the calls that read it with no arguments, by its type, such as "linear_velocity:length()" or "global_transform:get_origin()"; one it does not answer is refused, naming those it does. A string answers such calls too, "text:length()" or "text:strip_edges()", and "text:to_utf32_buffer():to_int32_array()" gives its code points, which tell a non-breaking space (160) from the space an answer shows in its place. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control's theme is read where it is drawn: 'get_theme_stylebox("panel"):content_margin_top' or 'get_theme_constant("separation")'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.`
|
|
18156
|
+
description: `property: which one to read, or a constant the node's script or a script it extends declares. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A key holding a colon, a bracket or a quote is written in double quotes, 'stock:"base:dew"', as a refusal listing the keys writes it. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A vector, a colour, a rectangle or a transform is stepped into by its components as GDScript names them, so one axis is one number: "size:x", "global_position:y", "modulate:a", "global_transform:origin:z"; a name the value does not have is refused, naming the ones it has. It also answers the calls that read it with no arguments, by its type, such as "linear_velocity:length()" or "global_transform:get_origin()"; one it does not answer is refused, naming those it does. A string answers such calls too, "text:length()" or "text:strip_edges()", and "text:to_utf32_buffer():to_int32_array()" gives its code points, which tell a non-breaking space (160) from the space an answer shows in its place. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that only reads (an engine method the engine marks as reading, or a game method not declared -> void, so queue_free() is refused) and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. An engine method that reads takes literal arguments in its brackets, words in double quotes, numbers, true, false or null, so a control's theme is read where it is drawn: 'get_theme_stylebox("panel"):content_margin_top' or 'get_theme_constant("separation")'. A game method is given none in a path, since nothing marks one as only reading; runtime_invoke call takes those. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.`
|
|
18069
18157
|
},
|
|
18070
18158
|
depth: {
|
|
18071
18159
|
type: "integer",
|
|
@@ -41762,6 +41850,30 @@ function cutTestRunNote(run) {
|
|
|
41762
41850
|
}
|
|
41763
41851
|
var init_cut_test_run = () => {};
|
|
41764
41852
|
|
|
41853
|
+
// src/godot-variant.ts
|
|
41854
|
+
var FLAG_64, MOST_MESSAGE_BYTES;
|
|
41855
|
+
var init_godot_variant = __esm(() => {
|
|
41856
|
+
FLAG_64 = 1 << 16;
|
|
41857
|
+
MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
41858
|
+
});
|
|
41859
|
+
|
|
41860
|
+
// src/game-debugger.ts
|
|
41861
|
+
var PAUSE_MS = 30000, STEP_MS = 1e4, RUNTIME_ADDON2 = "res://addons/gdharness_runtime/", RUNTIME_AUTOLOAD2, READ_MS = 5000, EVALUATE_MS = 60000, REPLIES;
|
|
41862
|
+
var init_game_debugger = __esm(() => {
|
|
41863
|
+
init_godot_variant();
|
|
41864
|
+
init_setup();
|
|
41865
|
+
RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON2}runtime_autoload.gd`;
|
|
41866
|
+
REPLIES = new Set([
|
|
41867
|
+
"debug_enter",
|
|
41868
|
+
"debug_exit",
|
|
41869
|
+
"stack_dump",
|
|
41870
|
+
"stack_frame_vars",
|
|
41871
|
+
"stack_frame_var",
|
|
41872
|
+
"evaluation_return",
|
|
41873
|
+
"scene:inspect_object"
|
|
41874
|
+
]);
|
|
41875
|
+
});
|
|
41876
|
+
|
|
41765
41877
|
// src/dap_client.ts
|
|
41766
41878
|
import { createConnection as createConnection3 } from "node:net";
|
|
41767
41879
|
import { setTimeout as delay6 } from "node:timers/promises";
|
|
@@ -41804,11 +41916,13 @@ class GodotDAPClient {
|
|
|
41804
41916
|
onBreakpointsChanged = null;
|
|
41805
41917
|
scopesPatienceMs;
|
|
41806
41918
|
stepPatienceMs;
|
|
41919
|
+
evaluatePatienceMs;
|
|
41807
41920
|
constructor(port = portFromEnv("GDHARNESS_DAP_PORT", DEFAULT_DAP_PORT), host = "127.0.0.1", patience = {}) {
|
|
41808
41921
|
this.port = port;
|
|
41809
41922
|
this.host = host;
|
|
41810
41923
|
this.scopesPatienceMs = patience.scopesMs ?? SCOPES_TIMEOUT_MS;
|
|
41811
41924
|
this.stepPatienceMs = patience.stepMs ?? STEP_LANDS_WITHIN_MS;
|
|
41925
|
+
this.evaluatePatienceMs = patience.evaluateMs ?? EVALUATE_MS;
|
|
41812
41926
|
this.pendingRequests = new Map;
|
|
41813
41927
|
}
|
|
41814
41928
|
async connect() {
|
|
@@ -42312,7 +42426,7 @@ class GodotDAPClient {
|
|
|
42312
42426
|
if (members?.variables?.some((variable) => variable["name"] === "self") !== true) {
|
|
42313
42427
|
throw new ReadRefusal(`Frame ${frame} has no instance of its script to evaluate on, as a static function called on its class has none, and the engine lets the game go on rather than answer one there, so it was not asked. Evaluate in a frame below it, or read this one's locals with debug_state variables`);
|
|
42314
42428
|
}
|
|
42315
|
-
const deadline = Date.now() + this.
|
|
42429
|
+
const deadline = Date.now() + this.evaluatePatienceMs;
|
|
42316
42430
|
while (Date.now() < deadline) {
|
|
42317
42431
|
try {
|
|
42318
42432
|
const answered = await this.sendRequest("evaluate", { expression, frameId: frame, context: "watch" }, VARIABLES_POLL_TIMEOUT_MS);
|
|
@@ -42324,7 +42438,7 @@ class GodotDAPClient {
|
|
|
42324
42438
|
} catch {}
|
|
42325
42439
|
await delay6(200);
|
|
42326
42440
|
}
|
|
42327
|
-
throw new ReadRefusal(`What ${expression} comes to did not arrive from the game within ${this.
|
|
42441
|
+
throw new ReadRefusal(`What ${expression} comes to did not arrive from the game within ${this.evaluatePatienceMs / 1000}s. It comes from the game through the editor, and the first expression a game evaluates loads the script of every global class in the project first, static initialisers included, so one of those may still be running: ask again`);
|
|
42328
42442
|
}
|
|
42329
42443
|
isConnected() {
|
|
42330
42444
|
return this.connected;
|
|
@@ -42471,6 +42585,7 @@ var init_dap_client = __esm(() => {
|
|
|
42471
42585
|
init_answer_json();
|
|
42472
42586
|
init_errors();
|
|
42473
42587
|
init_framing();
|
|
42588
|
+
init_game_debugger();
|
|
42474
42589
|
init_ports();
|
|
42475
42590
|
ReadRefusal = class ReadRefusal extends Refusal {
|
|
42476
42591
|
};
|
|
@@ -44040,7 +44155,7 @@ import {
|
|
|
44040
44155
|
statSync as statSync17,
|
|
44041
44156
|
writeFileSync as writeFileSync19
|
|
44042
44157
|
} from "node:fs";
|
|
44043
|
-
import { basename as basename10, dirname as dirname22, extname as
|
|
44158
|
+
import { basename as basename10, dirname as dirname22, extname as extname4, join as join38, relative as relative8 } from "node:path";
|
|
44044
44159
|
function filesUnder(directory) {
|
|
44045
44160
|
return readdirSync17(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
44046
44161
|
const path = join38(directory, entry.name);
|
|
@@ -44160,10 +44275,10 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
44160
44275
|
if (owner !== undefined && owner !== null) {
|
|
44161
44276
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
44162
44277
|
}
|
|
44163
|
-
if (
|
|
44278
|
+
if (extname4(from).toLowerCase() !== extname4(to).toLowerCase()) {
|
|
44164
44279
|
return {
|
|
44165
44280
|
ok: false,
|
|
44166
|
-
reason: `${from} would become ${to}, and the engine picks how to read a file by its extension: give newPath the extension it has, ${
|
|
44281
|
+
reason: `${from} would become ${to}, and the engine picks how to read a file by its extension: give newPath the extension it has, ${extname4(from) || "none"}.`
|
|
44167
44282
|
};
|
|
44168
44283
|
}
|
|
44169
44284
|
}
|
|
@@ -44320,30 +44435,6 @@ var init_file_move = __esm(() => {
|
|
|
44320
44435
|
SIDECARS = [".uid", ".import"];
|
|
44321
44436
|
});
|
|
44322
44437
|
|
|
44323
|
-
// src/godot-variant.ts
|
|
44324
|
-
var FLAG_64, MOST_MESSAGE_BYTES;
|
|
44325
|
-
var init_godot_variant = __esm(() => {
|
|
44326
|
-
FLAG_64 = 1 << 16;
|
|
44327
|
-
MOST_MESSAGE_BYTES = (8 << 20) + 4;
|
|
44328
|
-
});
|
|
44329
|
-
|
|
44330
|
-
// src/game-debugger.ts
|
|
44331
|
-
var PAUSE_MS = 30000, STEP_MS = 1e4, RUNTIME_ADDON2 = "res://addons/gdharness_runtime/", RUNTIME_AUTOLOAD2, READ_MS = 5000, REPLIES;
|
|
44332
|
-
var init_game_debugger = __esm(() => {
|
|
44333
|
-
init_godot_variant();
|
|
44334
|
-
init_setup();
|
|
44335
|
-
RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON2}runtime_autoload.gd`;
|
|
44336
|
-
REPLIES = new Set([
|
|
44337
|
-
"debug_enter",
|
|
44338
|
-
"debug_exit",
|
|
44339
|
-
"stack_dump",
|
|
44340
|
-
"stack_frame_vars",
|
|
44341
|
-
"stack_frame_var",
|
|
44342
|
-
"evaluation_return",
|
|
44343
|
-
"scene:inspect_object"
|
|
44344
|
-
]);
|
|
44345
|
-
});
|
|
44346
|
-
|
|
44347
44438
|
// node_modules/ws/lib/constants.js
|
|
44348
44439
|
var require_constants = __commonJS(function(exports, module) {
|
|
44349
44440
|
var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
|
|
@@ -49221,7 +49312,7 @@ import {
|
|
|
49221
49312
|
statSync as statSync20,
|
|
49222
49313
|
writeFileSync as writeFileSync22
|
|
49223
49314
|
} from "node:fs";
|
|
49224
|
-
import { basename as basename13, dirname as dirname25, extname as
|
|
49315
|
+
import { basename as basename13, dirname as dirname25, extname as extname5, isAbsolute as isAbsolute8, join as join44, normalize as normalize2, relative as relative10 } from "node:path";
|
|
49225
49316
|
import { setTimeout as delay7 } from "node:timers/promises";
|
|
49226
49317
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
49227
49318
|
function editorRestartTimeoutMs() {
|
|
@@ -49273,7 +49364,7 @@ function writtenFileRefusal(path, rule) {
|
|
|
49273
49364
|
if (!isAbsolute8(path)) {
|
|
49274
49365
|
return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
|
|
49275
49366
|
}
|
|
49276
|
-
if (!rule.extensions.includes(
|
|
49367
|
+
if (!rule.extensions.includes(extname5(path).toLowerCase())) {
|
|
49277
49368
|
return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
|
|
49278
49369
|
}
|
|
49279
49370
|
const directory = realAsFarAsItExists(dirname25(path));
|
|
@@ -49305,7 +49396,7 @@ function movieRefusal(engineArgs, projects, headless) {
|
|
|
49305
49396
|
extensions: [".avi", ".png"],
|
|
49306
49397
|
projects,
|
|
49307
49398
|
writes: (where) => {
|
|
49308
|
-
if (
|
|
49399
|
+
if (extname5(where).toLowerCase() !== ".png") {
|
|
49309
49400
|
return [where];
|
|
49310
49401
|
}
|
|
49311
49402
|
const base = where.slice(0, -".png".length);
|
|
@@ -49620,16 +49711,6 @@ function endedPreviousRun(ended) {
|
|
|
49620
49711
|
function previousRunLeft(ended) {
|
|
49621
49712
|
return (ended?.ending === "refused" || ended?.ending === "lingering") && ended.pid !== null ? ended.pid : undefined;
|
|
49622
49713
|
}
|
|
49623
|
-
function editorsOfProjectIn(tree, projectPath) {
|
|
49624
|
-
const found = [];
|
|
49625
|
-
for (const [pid, listed] of tree) {
|
|
49626
|
-
const read = readCommandLine(listed.command);
|
|
49627
|
-
if (read.editor && read.oneShot === null && read.projectPath !== null && /godot/i.test(read.executable) && !CONSOLE_WRAPPER.test(read.executable) && isSameDirectory(read.projectPath, projectPath)) {
|
|
49628
|
-
found.push(pid);
|
|
49629
|
-
}
|
|
49630
|
-
}
|
|
49631
|
-
return found.sort((a, b) => a - b);
|
|
49632
|
-
}
|
|
49633
49714
|
function engineNamesOf(executable) {
|
|
49634
49715
|
return CONSOLE_WRAPPER.test(executable) ? [executable, executable.replace(CONSOLE_WRAPPER, "$1")] : [executable];
|
|
49635
49716
|
}
|
|
@@ -52643,6 +52724,9 @@ class GodotServer {
|
|
|
52643
52724
|
if (forValidation.length > 0 && !include.includes("validation")) {
|
|
52644
52725
|
return this.createErrorResponse(`${forValidation.join(" and ")} ${forValidation.length === 1 ? "is" : "are"} read only by the validation section, which this call does not include: add "validation" to include, or leave ${forValidation.length === 1 ? "it" : "them"} out.`);
|
|
52645
52726
|
}
|
|
52727
|
+
if (args["addonClasses"] !== undefined && !include.includes("classes")) {
|
|
52728
|
+
return this.createErrorResponse('addonClasses is read only by the classes section, which this call does not include: add "classes" to include, or leave it out.');
|
|
52729
|
+
}
|
|
52646
52730
|
const project = this.project(args);
|
|
52647
52731
|
if (!project.ok) {
|
|
52648
52732
|
return project.response;
|
|
@@ -52673,6 +52757,23 @@ class GodotServer {
|
|
|
52673
52757
|
...compiledScripts !== undefined && (section === "health" || section === "validation") ? { compiled: compiledScripts } : {}
|
|
52674
52758
|
};
|
|
52675
52759
|
const outcome = await this.operation(spec.operation, params, project.value.path);
|
|
52760
|
+
if (section === "classes" && outcome.ok && Array.isArray(outcome.payload["classes"])) {
|
|
52761
|
+
const declared = outcome.payload["classes"];
|
|
52762
|
+
const asked = readStringArray(args, "addonClasses") ?? [];
|
|
52763
|
+
const addons = Object.keys(classesWithAddonsCounted(declared, new Set).inAddons).sort();
|
|
52764
|
+
const unknownAddons = asked.filter((addon) => !addons.includes(addon));
|
|
52765
|
+
if (unknownAddons.length > 0) {
|
|
52766
|
+
return this.createErrorResponse(`addonClasses names ${unknownAddons.join(", ")}, and no folder of that name under res://addons declares a class. ${addons.length === 0 ? "No addon declares one." : `The addons declaring classes are ${addons.join(", ")}.`}`);
|
|
52767
|
+
}
|
|
52768
|
+
const counted = classesWithAddonsCounted(declared, new Set(asked));
|
|
52769
|
+
info[section] = {
|
|
52770
|
+
...outcome.payload,
|
|
52771
|
+
classes: counted.classes,
|
|
52772
|
+
...Object.keys(counted.inAddons).length > 0 ? { inAddons: counted.inAddons } : {},
|
|
52773
|
+
...engineExtras(outcome)
|
|
52774
|
+
};
|
|
52775
|
+
continue;
|
|
52776
|
+
}
|
|
52676
52777
|
info[section] = outcome.ok ? { ...outcome.payload, ...engineExtras(outcome) } : { error: outcome.message, ...engineExtras(outcome) };
|
|
52677
52778
|
}
|
|
52678
52779
|
return this.jsonTextResponse(info);
|
|
@@ -52887,7 +52988,7 @@ ${platform}`;
|
|
|
52887
52988
|
if (pack && debug) {
|
|
52888
52989
|
return this.createErrorResponse("A pack is exported as a release: the command-line exporter has no debug pack, so debug and pack cannot both be true.", ["Leave debug out for a pack", "Or leave pack out for a debug build"]);
|
|
52889
52990
|
}
|
|
52890
|
-
const extension =
|
|
52991
|
+
const extension = extname5(output.relativePath).toLowerCase();
|
|
52891
52992
|
if (pack && extension !== ".pck" && extension !== ".zip") {
|
|
52892
52993
|
return this.createErrorResponse(`A pack is written as a .pck or a .zip, and ${output.relativePath} is neither: the exporter picks the format by the extension.`, ["Give outputPath ending in .pck, or .zip for a zip of the resources"]);
|
|
52893
52994
|
}
|
|
@@ -53998,7 +54099,7 @@ ${error.text}`;
|
|
|
53998
54099
|
return { ...game, keeper: game.keeper };
|
|
53999
54100
|
}
|
|
54000
54101
|
async askTheKeptGame(keeper, request, projectPath) {
|
|
54001
|
-
const answered = await askTheKeepersDebugger(keeper, request, KEEPER_DEBUG_MS);
|
|
54102
|
+
const answered = await askTheKeepersDebugger(keeper, request, request.op === "evaluate" ? EVALUATE_MS + 2 * READ_MS : KEEPER_DEBUG_MS);
|
|
54002
54103
|
if ("unreached" in answered) {
|
|
54003
54104
|
return this.createErrorResponse(`The debugger of the running game did not answer: ${String(answered.unreached)}.`, [
|
|
54004
54105
|
"editor_run start starts the game again, with a debugger",
|
|
@@ -57522,6 +57623,7 @@ ${error.text}`;
|
|
|
57522
57623
|
async settleRenameOnDisk(projectPath, changed, asked) {
|
|
57523
57624
|
const report = {};
|
|
57524
57625
|
let ok = true;
|
|
57626
|
+
const cacheBefore = asked.rebuildCache ? cachedEntries(projectPath) : null;
|
|
57525
57627
|
if (asked.moved) {
|
|
57526
57628
|
const engine = await this.engine();
|
|
57527
57629
|
const sidecarsBefore = new Set(projectFiles(projectPath, [".uid", ".import"]));
|
|
@@ -57536,7 +57638,7 @@ ${error.text}`;
|
|
|
57536
57638
|
}
|
|
57537
57639
|
if (asked.rebuildCache) {
|
|
57538
57640
|
const rebuilt = await this.rebuildClassCache(projectPath);
|
|
57539
|
-
report["classCache"] = rebuilt.ok ? rebuilt.payload : { rebuilt: false, problem: rebuilt.message };
|
|
57641
|
+
report["classCache"] = rebuilt.ok ? { ...rebuilt.payload, ...cacheChangesBetween(cacheBefore, cachedEntries(projectPath)) } : { rebuilt: false, problem: rebuilt.message };
|
|
57540
57642
|
ok &&= rebuilt.ok;
|
|
57541
57643
|
}
|
|
57542
57644
|
if (changed.length > 0) {
|
|
@@ -58102,7 +58204,7 @@ async function runGodotServer() {
|
|
|
58102
58204
|
const server = new GodotServer;
|
|
58103
58205
|
await server.run();
|
|
58104
58206
|
}
|
|
58105
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000,
|
|
58207
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, SLOW_ANSWER_MS = 1000, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_TIMEOUT_MS = 600000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. 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 listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
58106
58208
|
var init_server = __esm(() => {
|
|
58107
58209
|
init_dist2();
|
|
58108
58210
|
init_stdio();
|
|
@@ -58171,7 +58273,6 @@ var init_server = __esm(() => {
|
|
|
58171
58273
|
init_update_check();
|
|
58172
58274
|
__dirname2 = dirname25(fileURLToPath5(import.meta.url));
|
|
58173
58275
|
KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
|
|
58174
|
-
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
58175
58276
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
58176
58277
|
VALUE_NAMES = {
|
|
58177
58278
|
string: "a string",
|
|
@@ -58461,6 +58562,7 @@ async function pipe(port) {
|
|
|
58461
58562
|
|
|
58462
58563
|
// src/cli.ts
|
|
58463
58564
|
init_paths();
|
|
58565
|
+
init_process_children();
|
|
58464
58566
|
|
|
58465
58567
|
// src/prompt.ts
|
|
58466
58568
|
import process12 from "node:process";
|
|
@@ -60317,10 +60419,8 @@ async function upgrade() {
|
|
|
60317
60419
|
}
|
|
60318
60420
|
console.log(`
|
|
60319
60421
|
On ${version}. What is left for you:`);
|
|
60320
|
-
|
|
60321
|
-
|
|
60322
|
-
` + " if the editor is running other code." : ` 1. The open editor is still running the addons it loaded at startup. Restart it with the
|
|
60323
|
-
` + " editor_launch restart tool, which closes and reopens the window.");
|
|
60422
|
+
const tree = await processTree();
|
|
60423
|
+
console.log(editorUpgradeNote(digestBefore !== null && digestBefore === installedEditorDigest(projectPath), tree === undefined ? null : editorsOfProjectIn(tree, projectPath)));
|
|
60324
60424
|
const serving = await serverServing(projectPath);
|
|
60325
60425
|
console.log(harnessNote({ moved, written, byHand }, [launch.command, ...launch.args].join(" "), serving === null ? null : { server: serving, isThisVersion: serving.version === version }));
|
|
60326
60426
|
const trailing = worktreeCheckoutsOf(projectPath).flatMap((path) => {
|