gdharness 1.0.30 → 1.0.32

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 CHANGED
@@ -204,7 +204,9 @@ function ancestorsIn(tree, pid, root) {
204
204
  return chain;
205
205
  }
206
206
  function readCommandLine(command) {
207
- const words = commandWords(command);
207
+ const all = commandWords(command);
208
+ const separator = all.findIndex((word) => word === "--" || word === "++");
209
+ const words = separator === -1 ? all : all.slice(0, separator);
208
210
  const executable = words[0] === undefined ? "" : basename(words[0].replaceAll("\\", "/"));
209
211
  const at = words.indexOf("--path");
210
212
  let projectPath = null;
@@ -216,7 +218,7 @@ function readCommandLine(command) {
216
218
  }
217
219
  taken.push(word);
218
220
  }
219
- projectPath = taken.join(" ");
221
+ projectPath = taken.join(" ").replaceAll("%20", " ");
220
222
  }
221
223
  return {
222
224
  executable,
@@ -16356,6 +16358,12 @@ var init_tool_definitions = __esm(() => {
16356
16358
  type: "boolean",
16357
16359
  ops: ["stop"],
16358
16360
  description: "stop: also end what the game started for itself, with OS.create_process or otherwise, such as a bench's workers and whatever those started in turn. Only the processes under the run's, to any depth, that are games of this project are ended: announced as one, or this project's engine run with --path on this project by its command line, which is how a bench whose workers deliberately carry no runtime is reached. They are named under endedChildren, with what identified each under identifiedBy; any other process under the run is left and named under childrenLeft, and childrenUnknown is true when nothing could be listed, with the note saying whether the platform would not or the run has no process to list under. Default false, and the run's process is ended either way."
16361
+ },
16362
+ pid: {
16363
+ type: "integer",
16364
+ minimum: 1,
16365
+ ops: ["stop"],
16366
+ description: "stop: a game of this project that no server here is holding, by its process id as editor_status lists it under runtimes: one a server before a reconnect started and left no note for, or one started some other way. It is ended only when it announced itself for this project and the operating system describes it as this project's engine run, --path on the project and no editor flag, started before it announced; otherwise nothing is signalled and the answer says which of those failed. The run this server holds is ended the ordinary way whether or not its number is given."
16359
16367
  }
16360
16368
  },
16361
16369
  requires: [],
@@ -16365,7 +16373,7 @@ var init_tool_definitions = __esm(() => {
16365
16373
  requires: ["projectPath"]
16366
16374
  },
16367
16375
  stop: {
16368
- summary: "end the run and answer with what it printed last, naming the process ended under endedPid: 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. A game that started processes of its own keeps them unless andChildren says to end those too",
16376
+ summary: "end the run and answer with what it printed last, naming the process ended under endedPid: 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. A game that started processes of its own keeps them unless andChildren says to end those too. 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",
16369
16377
  requires: []
16370
16378
  },
16371
16379
  check: {
@@ -16567,7 +16575,7 @@ var init_tool_definitions = __esm(() => {
16567
16575
  operations: {
16568
16576
  tree: { summary: "the live scene tree", requires: [] },
16569
16577
  text: {
16570
- summary: "every line of text under nodePath, the values in its fields included, in the order somebody reads the screen, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without its tags, text only as far as it has been typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items read a line each",
16578
+ summary: "every line of text under nodePath, the values in its fields included, in the order somebody reads the screen, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without its tags, text only as far as it has been typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items and the titles of its separators read a line each",
16571
16579
  requires: []
16572
16580
  },
16573
16581
  find: {
@@ -31703,8 +31711,13 @@ function parseAnnouncement(file, pid) {
31703
31711
  name: readString(project, "name") ?? "",
31704
31712
  path: readString(project, "path") ?? ""
31705
31713
  };
31714
+ let announcedAt;
31715
+ try {
31716
+ announcedAt = statSync5(file).mtimeMs;
31717
+ } catch {}
31718
+ const when = announcedAt === undefined ? {} : { announcedAt };
31706
31719
  if (protocol !== RUNTIME_PROTOCOL) {
31707
- return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named } };
31720
+ return { kind: "unspoken", unspoken: { pid, protocol: protocol ?? 0, project: named, ...when } };
31708
31721
  }
31709
31722
  const editorPid = readNumber(fields, "editor_pid");
31710
31723
  return {
@@ -31715,7 +31728,8 @@ function parseAnnouncement(file, pid) {
31715
31728
  address,
31716
31729
  project: named,
31717
31730
  file,
31718
- ...editorPid !== undefined && Number.isInteger(editorPid) && editorPid > 0 ? { editorPid } : {}
31731
+ ...editorPid !== undefined && Number.isInteger(editorPid) && editorPid > 0 ? { editorPid } : {},
31732
+ ...when
31719
31733
  }
31720
31734
  };
31721
31735
  }
@@ -37733,8 +37747,25 @@ var init_lsp_client = __esm(() => {
37733
37747
  // src/outside.ts
37734
37748
  import { spawn } from "node:child_process";
37735
37749
  import { fileURLToPath as fileURLToPath5 } from "node:url";
37736
- function encodeSpec(spec) {
37737
- return Buffer.from(JSON.stringify(spec), "utf8").toString("base64url");
37750
+ function changesFrom(env, base) {
37751
+ const changes = {};
37752
+ if (env === undefined) {
37753
+ return changes;
37754
+ }
37755
+ for (const [name, value] of Object.entries(env)) {
37756
+ if (value !== undefined && base[name] !== value) {
37757
+ changes[name] = value;
37758
+ }
37759
+ }
37760
+ for (const name of Object.keys(base)) {
37761
+ if (env[name] === undefined) {
37762
+ changes[name] = null;
37763
+ }
37764
+ }
37765
+ return changes;
37766
+ }
37767
+ function sendSpec(child, spec) {
37768
+ child.stdin?.end(JSON.stringify(spec), "utf8");
37738
37769
  }
37739
37770
  function readLaunched(printed) {
37740
37771
  const line = printed.split(`
@@ -37747,10 +37778,16 @@ function readLaunched(printed) {
37747
37778
  }
37748
37779
  async function launchOutsideTheTree(spec, runtime = process.execPath, keeper = KEEPER_SCRIPT) {
37749
37780
  return await new Promise((resolve) => {
37750
- const launcher = spawn(runtime, [keeper, "launch", encodeSpec(spec)], {
37751
- stdio: ["ignore", "pipe", "pipe"],
37781
+ const launcher = spawn(runtime, [keeper, "launch"], {
37782
+ stdio: ["pipe", "pipe", "pipe"],
37752
37783
  windowsHide: true
37753
37784
  });
37785
+ sendSpec(launcher, {
37786
+ command: spec.command,
37787
+ args: spec.args,
37788
+ envChanges: changesFrom(spec.env, process.env),
37789
+ ...spec.run === undefined ? {} : { run: spec.run }
37790
+ });
37754
37791
  let printed = "";
37755
37792
  let complained = "";
37756
37793
  launcher.stdout.on("data", (chunk) => {
@@ -37987,30 +38024,61 @@ var init_restart_note = __esm(() => {
37987
38024
 
37988
38025
  // src/run-record.ts
37989
38026
  import { execFileSync as execFileSync2 } from "node:child_process";
37990
- import { mkdirSync as mkdirSync13, openSync as openSync2, readdirSync as readdirSync8, readFileSync as readFileSync20, rmSync as rmSync11, statSync as statSync7, writeFileSync as writeFileSync15 } from "node:fs";
37991
- import { basename as basename3, join as join22 } from "node:path";
38027
+ import { createHash as createHash5 } from "node:crypto";
38028
+ import {
38029
+ mkdirSync as mkdirSync13,
38030
+ openSync as openSync2,
38031
+ readdirSync as readdirSync8,
38032
+ readFileSync as readFileSync20,
38033
+ realpathSync as realpathSync3,
38034
+ rmSync as rmSync11,
38035
+ statSync as statSync7,
38036
+ writeFileSync as writeFileSync15
38037
+ } from "node:fs";
38038
+ import { basename as basename3, dirname as dirname16, join as join22, resolve as resolve5 } from "node:path";
37992
38039
  function runsDirectory() {
37993
38040
  return join22(runtimeDirectory(), "runs");
37994
38041
  }
37995
- function recordPaths() {
37996
- return runtimeDirectories().map((directory) => join22(directory, "runs", "run.json"));
38042
+ function realAsFarAsItExists(path) {
38043
+ const missing = [];
38044
+ let at = path;
38045
+ for (;; ) {
38046
+ try {
38047
+ return join22(realpathSync3.native(at), ...missing);
38048
+ } catch {
38049
+ const parent = dirname16(at);
38050
+ if (parent === at) {
38051
+ return path;
38052
+ }
38053
+ missing.unshift(basename3(at));
38054
+ at = parent;
38055
+ }
38056
+ }
38057
+ }
38058
+ function projectKey(projectPath) {
38059
+ const real = realAsFarAsItExists(resolve5(projectPath)).replace(/[\\/]+$/, "");
38060
+ const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
38061
+ return createHash5("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
38062
+ }
38063
+ function runRecordPath(projectPath, directory = runtimeDirectory()) {
38064
+ return join22(directory, "runs", `note-${projectKey(projectPath)}.json`);
37997
38065
  }
37998
38066
  function openTranscript(startedAt) {
37999
38067
  mkdirSync13(runsDirectory(), { recursive: true });
38000
38068
  const path = join22(runsDirectory(), `run-${startedAt}.log`);
38001
38069
  return { path, fd: openSync2(path, "a") };
38002
38070
  }
38003
- function editorNotePath() {
38004
- return join22(runsDirectory(), "editor-run.json");
38071
+ function editorNotePath(projectPath, directory = runtimeDirectory()) {
38072
+ return join22(directory, "runs", `editor-${projectKey(projectPath)}.json`);
38005
38073
  }
38006
38074
  function writeEditorRunNote(note) {
38007
38075
  mkdirSync13(runsDirectory(), { recursive: true });
38008
- writeFileSync15(editorNotePath(), JSON.stringify(note, null, 2), "utf8");
38076
+ writeFileSync15(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
38009
38077
  }
38010
- function readEditorRunNote() {
38078
+ function readEditorRunNote(projectPath) {
38011
38079
  for (const directory of runtimeDirectories()) {
38012
- const note = editorNoteAt(join22(directory, "runs", "editor-run.json"));
38013
- if (note !== null) {
38080
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join22(directory, "runs", "editor-run.json"));
38081
+ if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
38014
38082
  return note;
38015
38083
  }
38016
38084
  }
@@ -38035,15 +38103,39 @@ function editorNoteAt(path) {
38035
38103
  }
38036
38104
  return { projectPath, transcript, startedAt };
38037
38105
  }
38038
- function readRunRecord() {
38039
- for (const path of recordPaths()) {
38040
- const record = recordAt(path);
38041
- if (record !== null) {
38042
- return record;
38106
+ function readRunRecord(projectPath) {
38107
+ for (const directory of runtimeDirectories()) {
38108
+ const own = recordAt(runRecordPath(projectPath, directory));
38109
+ if (own !== null) {
38110
+ return own;
38111
+ }
38112
+ const shared = recordAt(join22(directory, "runs", SHARED_NOTE));
38113
+ if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
38114
+ return shared;
38043
38115
  }
38044
38116
  }
38045
38117
  return null;
38046
38118
  }
38119
+ function everyRunRecord() {
38120
+ const found = [];
38121
+ for (const directory of runtimeDirectories()) {
38122
+ let entries;
38123
+ try {
38124
+ entries = readdirSync8(join22(directory, "runs"));
38125
+ } catch {
38126
+ continue;
38127
+ }
38128
+ for (const entry of entries) {
38129
+ if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
38130
+ const record = recordAt(join22(directory, "runs", entry));
38131
+ if (record !== null) {
38132
+ found.push(record);
38133
+ }
38134
+ }
38135
+ }
38136
+ }
38137
+ return found.sort((one, other) => other.startedAt - one.startedAt);
38138
+ }
38047
38139
  function recordAt(path) {
38048
38140
  let parsed;
38049
38141
  try {
@@ -38201,7 +38293,7 @@ function judgeRun(record, running, needed) {
38201
38293
  if (wanted !== null && !said.includes(wanted)) {
38202
38294
  return false;
38203
38295
  }
38204
- if (running.kind === "commandLine" && AN_EDITOR.test(said)) {
38296
+ if (running.kind === "commandLine" && readCommandLine(running.text).editor) {
38205
38297
  return false;
38206
38298
  }
38207
38299
  if (record.projectPath === "") {
@@ -38213,15 +38305,32 @@ function judgeRun(record, running, needed) {
38213
38305
  const project = process.platform === "win32" ? record.projectPath.toLowerCase() : record.projectPath;
38214
38306
  return said.includes(project);
38215
38307
  }
38216
- function clearRunRecord(ours = () => true) {
38217
- for (const path of recordPaths()) {
38218
- const record = recordAt(path);
38219
- if (record === null || !ours(record)) {
38220
- continue;
38308
+ function stillTheAnnouncedGame(pid, projectPath, announcedAt) {
38309
+ return judgeAnnouncedGame(runningAs(pid), projectPath, announcedAt);
38310
+ }
38311
+ function judgeAnnouncedGame(running, projectPath, announcedAt) {
38312
+ if (running?.kind !== "commandLine" || running.startedAt === undefined) {
38313
+ return null;
38314
+ }
38315
+ if (running.startedAt - announcedAt > STARTED_AFTER_ANNOUNCING_MS) {
38316
+ return null;
38317
+ }
38318
+ const read = readCommandLine(running.text);
38319
+ if (read.editor || read.projectPath === null || !isSameDirectory(read.projectPath, projectPath)) {
38320
+ return null;
38321
+ }
38322
+ return read;
38323
+ }
38324
+ function clearRunRecord(projectPath) {
38325
+ for (const directory of runtimeDirectories()) {
38326
+ const shared = join22(directory, "runs", SHARED_NOTE);
38327
+ const sharedRecord = recordAt(shared);
38328
+ const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
38329
+ for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
38330
+ try {
38331
+ rmSync11(path, { force: true });
38332
+ } catch {}
38221
38333
  }
38222
- try {
38223
- rmSync11(path);
38224
- } catch {}
38225
38334
  }
38226
38335
  }
38227
38336
  function ageMs(path, now) {
@@ -38233,7 +38342,7 @@ function ageMs(path, now) {
38233
38342
  }
38234
38343
  function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
38235
38344
  const directory = runsDirectory();
38236
- const keep = readRunRecord()?.transcript;
38345
+ const kept = new Set(everyRunRecord().map((record) => record.transcript));
38237
38346
  let entries;
38238
38347
  try {
38239
38348
  entries = readdirSync8(directory);
@@ -38245,7 +38354,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
38245
38354
  continue;
38246
38355
  }
38247
38356
  const path = join22(directory, entry);
38248
- if (path === keep || ageMs(path, now) < keepMs) {
38357
+ if (kept.has(path) || ageMs(path, now) < keepMs) {
38249
38358
  continue;
38250
38359
  }
38251
38360
  try {
@@ -38253,11 +38362,11 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
38253
38362
  } catch {}
38254
38363
  }
38255
38364
  }
38256
- var AN_EDITOR, SAME_RUN_WINDOW_MS = 1e4;
38365
+ var SHARED_NOTE = "run.json", SAME_RUN_WINDOW_MS = 1e4;
38257
38366
  var init_run_record = __esm(() => {
38367
+ init_paths();
38258
38368
  init_process_children();
38259
38369
  init_runtime_client();
38260
- AN_EDITOR = /(?:^|\s)(?:-e|--editor)(?:\s|$)/;
38261
38370
  });
38262
38371
 
38263
38372
  // src/server.ts
@@ -38280,7 +38389,7 @@ import {
38280
38389
  statSync as statSync8
38281
38390
  } from "node:fs";
38282
38391
  import { tmpdir as tmpdir5 } from "node:os";
38283
- import { basename as basename4, dirname as dirname16, extname as extname2, isAbsolute as isAbsolute3, join as join23, normalize as normalize3, relative as relative3 } from "node:path";
38392
+ import { basename as basename4, dirname as dirname17, extname as extname2, isAbsolute as isAbsolute3, join as join23, normalize as normalize3, relative as relative3 } from "node:path";
38284
38393
  import { setTimeout as delay3 } from "node:timers/promises";
38285
38394
  import { fileURLToPath as fileURLToPath6 } from "node:url";
38286
38395
  import { promisify as promisify7 } from "node:util";
@@ -38294,9 +38403,9 @@ function captureDestinationRefusal(outputPath, projects) {
38294
38403
  if (extname2(outputPath).toLowerCase() !== ".png") {
38295
38404
  return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
38296
38405
  }
38297
- const directory = realPathOr(dirname16(outputPath));
38406
+ const directory = realPathOr(dirname17(outputPath));
38298
38407
  if (!existsSync15(directory)) {
38299
- return `outputPath's directory ${dirname16(outputPath)} does not exist; a capture creates no directories.`;
38408
+ return `outputPath's directory ${dirname17(outputPath)} does not exist; a capture creates no directories.`;
38300
38409
  }
38301
38410
  const where = join23(directory, basename4(outputPath));
38302
38411
  const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
@@ -38401,12 +38510,41 @@ function endedWithoutACode(run) {
38401
38510
  }
38402
38511
  return `The editor stopped playing this run. ${kept} What it printed is below.`;
38403
38512
  }
38513
+ function stopVerdict(stop) {
38514
+ if (!stop.wasRunning) {
38515
+ return {
38516
+ stopped: true,
38517
+ endedPid: stop.endedPid,
38518
+ clean: stop.errors === 0,
38519
+ note: "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running.",
38520
+ withChildren: false
38521
+ };
38522
+ }
38523
+ if (stop.ending === "refused") {
38524
+ return {
38525
+ stopped: false,
38526
+ notSignalled: stop.endedPid,
38527
+ note: `Nothing was ended: pid ${stop.endedPid ?? "unknown"} no longer answers as the process this run was started as (its engine, its project and when it started), so it was not signalled. If that process is still the game, end it yourself, for instance with runtime_invoke calling get_tree().quit() when the game has the runtime addon; if it is not, it belongs to something else.`,
38528
+ withChildren: false
38529
+ };
38530
+ }
38531
+ return {
38532
+ stopped: true,
38533
+ endedPid: stop.endedPid,
38534
+ clean: stop.errors === 0,
38535
+ note: `${stop.throughEditor ? stop.endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."}${stop.exitSignal === null ? "" : ` It was ended by ${stop.exitSignal} and so has no exit code.`}${stop.ending === "gone" ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`}`,
38536
+ withChildren: true
38537
+ };
38538
+ }
38404
38539
  function endedPreviousRun(ended) {
38405
- if (ended === null) {
38540
+ if (ended === null || ended.ending === "refused") {
38406
38541
  return;
38407
38542
  }
38408
38543
  return ended.pid ?? true;
38409
38544
  }
38545
+ function previousRunLeft(ended) {
38546
+ return ended?.ending === "refused" && ended.pid !== null ? ended.pid : undefined;
38547
+ }
38410
38548
  function engineNamesOf(executable) {
38411
38549
  return CONSOLE_WRAPPER.test(executable) ? [executable, executable.replace(CONSOLE_WRAPPER, "$1")] : [executable];
38412
38550
  }
@@ -38446,6 +38584,17 @@ function aboutTheChildren(ended, throughEditor) {
38446
38584
  const leftToo = ended.left.length === 0 ? "" : ` ${counted(ended.left.length)} it had started ${ended.left.length === 1 ? "was" : "were"} left, named under childrenLeft: neither announced as a game of this project nor this project's engine run with --path on it, so what ${ended.left.length === 1 ? "it is" : "they are"} cannot be told from here.`;
38447
38585
  return `${endedToo}${leftToo}`;
38448
38586
  }
38587
+ function childrenFields(ended) {
38588
+ if (ended === null) {
38589
+ return {};
38590
+ }
38591
+ return {
38592
+ endedChildren: ended.ended.map((one) => one.pid),
38593
+ identifiedBy: ended.ended.length === 0 ? undefined : Object.fromEntries(ended.ended.map((one) => [String(one.pid), one.identifiedBy])),
38594
+ childrenLeft: ended.left.length === 0 ? undefined : ended.left,
38595
+ childrenUnknown: ended.unknown === false ? undefined : true
38596
+ };
38597
+ }
38449
38598
  function uidsLeftNote(left) {
38450
38599
  if (left === 0) {
38451
38600
  return "No scene was written: this reads the project and imports it, and does not resave anything.";
@@ -38453,12 +38602,24 @@ function uidsLeftNote(left) {
38453
38602
  const them = left === 1 ? "it" : "them";
38454
38603
  return `${left === 1 ? "One file" : `${left} files`} still ${left === 1 ? "has" : "have"} no .uid, named under stillWithoutUid, which is the engine declining to import ${them} rather than this op skipping ${them}. No scene was written.`;
38455
38604
  }
38605
+ function leftRunningNote(stranded) {
38606
+ if (stranded.length === 0) {
38607
+ return "";
38608
+ }
38609
+ const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
38610
+ const [only] = stranded;
38611
+ return stranded.length === 1 && only !== undefined ? ` A game of this project was already running and still is: ${named}. Starting this one did not end it, because this server did not start it. editor_run stop with pid ${only.pid} ends it and leaves this run going.` : ` ${stranded.length} games of this project were already running and still are: ${named}. Starting this one did not end them, because this server did not start them. editor_run stop with the pid of each ends it and leaves this run going.`;
38612
+ }
38456
38613
  function endedToStartThis(ended) {
38457
38614
  if (ended === null) {
38458
38615
  return "";
38459
38616
  }
38460
38617
  const which = ended.pid === null ? "The game the editor was playing" : `The run that was going, pid ${ended.pid},`;
38461
- return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
38618
+ if (ended.ending === "refused") {
38619
+ return ` ${which} was not ended: that pid no longer answers as the process the run was started as, so nothing was signalled. If it is still the game, it is running beside this one and you have to end it yourself; if it is not, it belongs to something else.`;
38620
+ }
38621
+ const after = ended.ending === "lingering" ? ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be running beside this one: editor_status says whether it is.` : "";
38622
+ return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.${after}`;
38462
38623
  }
38463
38624
  function runtimeVerdict(endpoint, after) {
38464
38625
  if (endpoint !== null) {
@@ -39763,11 +39924,11 @@ class GodotServer {
39763
39924
  if (!engine.ok) {
39764
39925
  return engine.response;
39765
39926
  }
39766
- const folder = dirname16(output.absolutePath);
39927
+ const folder = dirname17(output.absolutePath);
39767
39928
  try {
39768
39929
  mkdirSync14(folder, { recursive: true });
39769
39930
  } catch (error) {
39770
- return this.createErrorResponse(`Cannot create ${dirname16(output.relativePath)} to export into: ${errorMessage(error)}`);
39931
+ return this.createErrorResponse(`Cannot create ${dirname17(output.relativePath)} to export into: ${errorMessage(error)}`);
39771
39932
  }
39772
39933
  const debug = readBoolean(args, "debug") ?? false;
39773
39934
  const exportLogs = mkdtempSync3(join23(tmpdir5(), "gdharness-export-"));
@@ -39912,7 +40073,7 @@ class GodotServer {
39912
40073
  } finally {
39913
40074
  discard(reportsDir, userData);
39914
40075
  try {
39915
- rmdirSync(dirname16(reportsDir));
40076
+ rmdirSync(dirname17(reportsDir));
39916
40077
  } catch {}
39917
40078
  }
39918
40079
  const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
@@ -40574,8 +40735,10 @@ class GodotServer {
40574
40735
  const before = this.currentRun();
40575
40736
  if (before !== null && await this.runStillGoing(before)) {
40576
40737
  this.logDebug("Ending the running game before starting another");
40577
- ended = { pid: before.pid ?? this.announcedPidOf(before) ?? null };
40578
- await this.endActiveGame("editor_run start, which ends the run that was going before it starts another", true);
40738
+ ended = {
40739
+ pid: before.pid ?? this.announcedPidOf(before) ?? null,
40740
+ ending: await this.endActiveGame("editor_run start, which ends the run that was going before it starts another", true)
40741
+ };
40579
40742
  }
40580
40743
  const alreadyPlaying = new Set(discoverRuntimes().map((endpoint) => endpoint.pid));
40581
40744
  const wereHereFirst = this.everyRuntimeOfOurs();
@@ -40606,8 +40769,7 @@ class GodotServer {
40606
40769
  const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && given.value.length > 0 ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : "";
40607
40770
  const endedForThis = endedToStartThis(ended);
40608
40771
  const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
40609
- const named = stranded.map((one) => one.protocol === null ? `pid ${one.pid}` : `pid ${one.pid} on protocol ${one.protocol}`).join(", ");
40610
- const alsoRunning = stranded.length === 0 ? "" : ` A game of this project was already running and still is: ${named}. Starting this one did not end it and nothing here can, because this server did not start it. End it yourself, or bring the halves to one version where a protocol is named.`;
40772
+ const alsoRunning = leftRunningNote(stranded);
40611
40773
  return this.jsonTextResponse({
40612
40774
  started: true,
40613
40775
  through: "gdharness",
@@ -40619,6 +40781,7 @@ class GodotServer {
40619
40781
  ...scanWaitAnswer(scanned),
40620
40782
  savesNote: asked.value.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
40621
40783
  endedPreviousRun: endedPreviousRun(ended),
40784
+ previousRunLeft: previousRunLeft(ended),
40622
40785
  runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
40623
40786
  message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
40624
40787
  });
@@ -40751,6 +40914,7 @@ class GodotServer {
40751
40914
  ...scanWaitAnswer(scanned),
40752
40915
  debugPort: readNumber(playAnswer, "debugPort"),
40753
40916
  endedPreviousRun: endedPreviousRun(ended),
40917
+ previousRunLeft: previousRunLeft(ended),
40754
40918
  breakpoints: breakpoints.applied.length === 0 ? undefined : this.breakpointsAsProjectSpells(projectPath, breakpoints.applied),
40755
40919
  breakpointsRefused: breakpoints.refused.length === 0 ? undefined : breakpoints.refused.map((one) => ({
40756
40920
  scriptPath: projectSpelling(projectPath, one.scriptPath),
@@ -40825,31 +40989,41 @@ class GodotServer {
40825
40989
  }
40826
40990
  async endActiveGame(reason, going) {
40827
40991
  const running = this.activeProcess;
40828
- const recorded = running !== null && !running.throughEditor ? readRunRecord() : null;
40992
+ const project = running?.projectPath ?? this.ourProject();
40829
40993
  const clearTheRecord = () => {
40830
- clearRunRecord((record) => this.couldBeOurs(record.projectPath));
40994
+ if (project !== null) {
40995
+ clearRunRecord(project);
40996
+ }
40831
40997
  };
40832
40998
  const aKeeperHoldsIt = running !== null && going && !running.throughEditor && running.pid !== null;
40833
40999
  if (!aKeeperHoldsIt) {
40834
41000
  clearTheRecord();
40835
41001
  }
40836
41002
  if (!running || !going) {
40837
- return true;
41003
+ return "gone";
40838
41004
  }
40839
41005
  running.endedHere = reason;
40840
41006
  const announced = this.announcedPidOf(running);
40841
41007
  if (running.throughEditor) {
40842
41008
  await this.handleViaBridge("stop_playing", {});
40843
- return await untilGone(announced === undefined ? [] : [announced]);
41009
+ return await untilGone(announced === undefined ? [] : [announced]) ? "gone" : "lingering";
40844
41010
  }
40845
41011
  if (running.pid === null) {
40846
- return true;
40847
- }
40848
- if (recorded === null || recorded.pid !== running.pid || !stillTheRecordedRun(recorded)) {
41012
+ return "gone";
41013
+ }
41014
+ const startedAs = {
41015
+ pid: running.pid,
41016
+ transcript: running.transcript ?? "",
41017
+ startedAt: running.startedAt,
41018
+ projectPath: running.projectPath ?? "",
41019
+ arguments: [],
41020
+ ...running.command === undefined ? {} : { command: running.command }
41021
+ };
41022
+ if (!stillTheRecordedRun(startedAs)) {
40849
41023
  clearTheRecord();
40850
41024
  running.endedHere = null;
40851
- running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the run that was recorded, so nothing was signalled. If that process is still the game, end it yourself; if it is not, it belongs to something else.`);
40852
- return true;
41025
+ running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the process the run was started as, so nothing was signalled. If that process is still the game, end it yourself; if it is not, it belongs to something else.`);
41026
+ return "refused";
40853
41027
  }
40854
41028
  const signalled = runningAs(running.pid);
40855
41029
  const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
@@ -40869,7 +41043,7 @@ class GodotServer {
40869
41043
  }
40870
41044
  clearTheRecord();
40871
41045
  running.log.record("info", landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
40872
- return gone;
41046
+ return gone ? "gone" : "lingering";
40873
41047
  }
40874
41048
  spawnGame(godotPath, cmdArgs, env) {
40875
41049
  const child = spawn2(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
@@ -40945,6 +41119,7 @@ class GodotServer {
40945
41119
  readOffset: 0,
40946
41120
  projectPath,
40947
41121
  startedAt,
41122
+ command: godotPath,
40948
41123
  exitCode: null,
40949
41124
  exitSignal: null,
40950
41125
  throughEditor: false,
@@ -41002,8 +41177,7 @@ class GodotServer {
41002
41177
  this.logDebug(`Picked up an editor-played run without its adapter: ${errorMessage(error)}`);
41003
41178
  }
41004
41179
  const project = this.godotBridge.getStatus().projectPath ?? null;
41005
- const left = readEditorRunNote();
41006
- const note = left !== null && project !== null && isSameDirectory(left.projectPath, project) ? left : null;
41180
+ const note = project === null ? null : readEditorRunNote(project);
41007
41181
  const picked = {
41008
41182
  pid: null,
41009
41183
  log: new GameLog,
@@ -41136,7 +41310,8 @@ class GodotServer {
41136
41310
  return endpoint.editorPid === undefined || endpoint.editorPid !== editor;
41137
41311
  }
41138
41312
  adoptRecordedRun() {
41139
- const record = readRunRecord();
41313
+ const project = this.ourProject();
41314
+ const record = project === null ? null : readRunRecord(project);
41140
41315
  if (record === null || !this.couldBeOurs(record.projectPath)) {
41141
41316
  return null;
41142
41317
  }
@@ -41147,6 +41322,7 @@ class GodotServer {
41147
41322
  readOffset: 0,
41148
41323
  projectPath: record.projectPath === "" ? null : record.projectPath,
41149
41324
  startedAt: record.startedAt,
41325
+ ...record.command === undefined ? {} : { command: record.command },
41150
41326
  exitCode: null,
41151
41327
  exitSignal: null,
41152
41328
  throughEditor: false,
@@ -41168,18 +41344,18 @@ class GodotServer {
41168
41344
  const noEditor = !status.connected ? " No editor is connected, so a game the editor is playing cannot be seen from here at all:" + " editor_status says whether one is on its way, and a run of yours that is still up is" + " reachable again once it is." : "";
41169
41345
  const live = this.aRuntimeOfOursIsAnswering();
41170
41346
  if (live !== null) {
41171
- return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" + " editor_output have nothing to answer about. The runtime_* tools reach it now." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well." + (this.godotBridge.getStatus().connected ? "" : " editor_status says whether an editor is on its way, and editor_run can end it once one is.");
41347
+ return `A game is running for ${live.project.path} and has announced its runtime on port ${live.port}, pid ${live.pid},` + " but this server did not start it and cannot reach it through the editor, so editor_run and" + ` editor_output have nothing to answer about. The runtime_* tools reach it now, and editor_run stop with pid ${live.pid} ends it.` + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
41172
41348
  }
41173
41349
  const unreadable = this.aRuntimeOfOursIsTooFarOff();
41174
41350
  if (unreadable !== null) {
41175
41351
  const behind = whichHalfIsBehind([unreadable]);
41176
- return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` + " Until then neither editor_run nor the runtime_* tools can reach it." + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
41352
+ return `A game is running for ${unreadable.project.path}, pid ${unreadable.pid}, announced in protocol` + ` ${unreadable.protocol}, which this server does not speak: it speaks ${RUNTIME_PROTOCOL}, so ${behind}.` + ` Until then the runtime_* tools cannot reach it, and editor_run stop with pid ${unreadable.pid} ends it.` + " A run started here is a separate process rather than a replacement, so starting one leaves" + " this game running as well.";
41177
41353
  }
41178
- const record = readRunRecord();
41354
+ const mine = this.ourProject();
41355
+ const record = everyRunRecord().find((one) => mine === null || !isSameDirectory(one.projectPath, mine)) ?? null;
41179
41356
  if (record === null) {
41180
41357
  return `No game is running.${noEditor} Start one with editor_run.`;
41181
41358
  }
41182
- const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
41183
41359
  const whose = record.projectPath === "" ? "a project it does not name" : record.projectPath;
41184
41360
  const ours = mine === null ? "this server was started without GDHARNESS_PROJECT and has no editor connected, so it cannot say whose that run is" : `this server serves ${mine}`;
41185
41361
  return `No game of this server's is running.${noEditor} A run started from ${whose} is recorded on this machine, and ${ours}, so it is not this server's to answer for or to end. Start one with editor_run.`;
@@ -41222,13 +41398,16 @@ class GodotServer {
41222
41398
  ];
41223
41399
  }
41224
41400
  couldBeOurs(project) {
41225
- const status = this.godotBridge.getStatus();
41226
- const mine = this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
41401
+ const mine = this.ourProject();
41227
41402
  if (mine === null) {
41228
41403
  return false;
41229
41404
  }
41230
41405
  return project !== "" && isSameDirectory(mine, project);
41231
41406
  }
41407
+ ourProject() {
41408
+ const status = this.godotBridge.getStatus();
41409
+ return this.ownProject ?? (status.connected ? status.projectPath ?? null : null);
41410
+ }
41232
41411
  currentRun() {
41233
41412
  return this.activeProcess ?? this.adoptRecordedRun();
41234
41413
  }
@@ -41307,7 +41486,7 @@ class GodotServer {
41307
41486
  async untilTheExitIsRecorded(run) {
41308
41487
  const until = Date.now() + EXIT_REPORTED_WITHIN_MS;
41309
41488
  for (;; ) {
41310
- const record = readRunRecord();
41489
+ const record = run.projectPath === null ? null : readRunRecord(run.projectPath);
41311
41490
  if (record !== null && record.pid === run.pid && (record.exitCode !== undefined || record.exitSignal !== undefined)) {
41312
41491
  run.exitCode = record.exitCode ?? null;
41313
41492
  run.exitSignal = record.exitSignal ?? null;
@@ -41460,6 +41639,10 @@ class GodotServer {
41460
41639
  async handleStopProject(args) {
41461
41640
  await this.pickUpWhatTheEditorIsPlaying();
41462
41641
  const stopped = this.currentRun();
41642
+ const named = readNumber(args, "pid");
41643
+ if (named !== undefined && (stopped === null || named !== stopped.pid && named !== this.announcedPidOf(stopped))) {
41644
+ return await this.stopAnnouncedGame(named, readBoolean(args, "andChildren") === true);
41645
+ }
41463
41646
  if (!stopped) {
41464
41647
  return this.createErrorResponse(this.nothingOfOursIsRunning());
41465
41648
  }
@@ -41470,35 +41653,87 @@ class GodotServer {
41470
41653
  const endedPid = stopped.pid ?? announcedPid;
41471
41654
  const children = readBoolean(args, "andChildren") === true && wasRunning ? await this.whatTheRunStarted(stopped) : null;
41472
41655
  this.logDebug("Stopping the running game");
41473
- const gone = await this.endActiveGame("editor_run stop", wasRunning);
41474
- const ended = children === null ? null : endChildrenAmong(children);
41475
- if (wasRunning) {
41656
+ const ending = await this.endActiveGame("editor_run stop", wasRunning);
41657
+ const refused = ending === "refused";
41658
+ const ended = children === null || refused ? null : endChildrenAmong(children);
41659
+ if (wasRunning && !refused) {
41476
41660
  for (const pid of new Set([endedPid, announcedPid])) {
41477
41661
  if (pid !== null) {
41478
41662
  await announcementEnded(pid);
41479
41663
  }
41480
41664
  }
41481
41665
  }
41666
+ const verdict = stopVerdict({
41667
+ wasRunning,
41668
+ ending,
41669
+ endedPid,
41670
+ throughEditor: stopped.throughEditor,
41671
+ exitSignal: stopped.exitSignal,
41672
+ errors: stopped.log.count("error")
41673
+ });
41482
41674
  return this.jsonTextResponse({
41483
- stopped: true,
41675
+ stopped: verdict.stopped,
41484
41676
  through: stopped.throughEditor ? "editor" : "gdharness",
41485
- endedPid,
41486
- ...ended === null ? {} : {
41487
- endedChildren: ended.ended.map((one) => one.pid),
41488
- identifiedBy: ended.ended.length === 0 ? undefined : Object.fromEntries(ended.ended.map((one) => [String(one.pid), one.identifiedBy])),
41489
- childrenLeft: ended.left.length === 0 ? undefined : ended.left,
41490
- childrenUnknown: ended.unknown === false ? undefined : true
41491
- },
41677
+ endedPid: verdict.endedPid,
41678
+ notSignalled: verdict.notSignalled,
41679
+ ...childrenFields(ended),
41492
41680
  exitedBeforeStop: !wasRunning,
41493
41681
  exitCode: stopped.exitCode,
41494
41682
  exitSignal: stopped.exitSignal ?? undefined,
41495
41683
  errors: stopped.log.count("error"),
41496
41684
  warnings: stopped.log.count("warning"),
41497
- clean: stopped.log.count("error") === 0,
41498
- note: !wasRunning ? "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running." : `${stopped.throughEditor ? endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."}${stopped.exitSignal === null ? "" : ` It was ended by ${stopped.exitSignal} and so has no exit code.`}${gone ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`} ${aboutTheChildren(ended, stopped.throughEditor)}`,
41685
+ clean: verdict.clean,
41686
+ note: verdict.withChildren ? `${verdict.note} ${aboutTheChildren(ended, stopped.throughEditor)}` : verdict.note,
41499
41687
  entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
41500
41688
  });
41501
41689
  }
41690
+ async stopAnnouncedGame(pid, andChildren) {
41691
+ const announced = runtimesAnnounced();
41692
+ const game = [
41693
+ ...this.allAnnouncedForOurProject(announced.running),
41694
+ ...this.allAnnouncedForOurProject(announced.unspoken)
41695
+ ].find((one) => one.pid === pid);
41696
+ if (game === undefined) {
41697
+ return this.createErrorResponse(`pid ${pid} is not a game announced for this project, so nothing was signalled. editor_status lists this project's games under runtimes. A game that announces no runtime is ended by the server holding its run, or with andChildren by a stop of the run that started it.`);
41698
+ }
41699
+ const read = game.announcedAt === undefined ? null : stillTheAnnouncedGame(pid, game.project.path, game.announcedAt);
41700
+ if (read === null) {
41701
+ return this.createErrorResponse(`pid ${pid} announced itself as a game of this project, but the operating system does not describe it as this project's engine run started before that announcement (the engine with --path on the project and no editor flag), so nothing was signalled. If it is still the game, runtime_invoke can ask it to quit.`);
41702
+ }
41703
+ const run = {
41704
+ pid,
41705
+ log: new GameLog,
41706
+ transcript: null,
41707
+ readOffset: 0,
41708
+ projectPath: game.project.path,
41709
+ startedAt: game.announcedAt ?? Date.now(),
41710
+ exitCode: null,
41711
+ exitSignal: null,
41712
+ throughEditor: false,
41713
+ brokeOn: null,
41714
+ announcedPid: pid
41715
+ };
41716
+ const children = andChildren ? await this.whatTheRunStarted(run) : null;
41717
+ let landed = true;
41718
+ try {
41719
+ process.kill(pid);
41720
+ } catch {
41721
+ landed = false;
41722
+ }
41723
+ const gone = await untilGone([pid]);
41724
+ const ended = children === null ? null : endChildrenAmong(children);
41725
+ if (gone) {
41726
+ await announcementEnded(pid);
41727
+ }
41728
+ return this.jsonTextResponse({
41729
+ stopped: true,
41730
+ through: "gdharness",
41731
+ endedPid: pid,
41732
+ ...childrenFields(ended),
41733
+ exitedBeforeStop: !landed,
41734
+ note: `${landed ? `pid ${pid} was ended: a game of this project that no server here was holding,` : `pid ${pid} had already gone when it was signalled: a game of this project that no server here was holding,`} identified by its announcement and by its command line, ${read.executable} run with --path ${read.projectPath ?? ""}. Nothing here read what it printed or holds its exit code, so neither is in this answer.${gone ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`} ${aboutTheChildren(ended, false)}`
41735
+ });
41736
+ }
41502
41737
  async whatTheRunStarted(run) {
41503
41738
  const tree = await processTree();
41504
41739
  if (tree === undefined) {
@@ -42093,7 +42328,7 @@ var init_server3 = __esm(() => {
42093
42328
  init_tool_definitions();
42094
42329
  init_update_check();
42095
42330
  run7 = promisify7(execFile7);
42096
- __dirname2 = dirname16(fileURLToPath6(import.meta.url));
42331
+ __dirname2 = dirname17(fileURLToPath6(import.meta.url));
42097
42332
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
42098
42333
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
42099
42334
  VALUE_NAMES = {
@@ -42135,7 +42370,7 @@ var init_server3 = __esm(() => {
42135
42370
 
42136
42371
  // src/cli.ts
42137
42372
  import { existsSync as existsSync16 } from "fs";
42138
- import { join as join24, resolve as resolve5 } from "path";
42373
+ import { join as join24, resolve as resolve6 } from "path";
42139
42374
 
42140
42375
  // src/bridge-announce.ts
42141
42376
  init_errors();
@@ -43764,6 +43999,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
43764
43999
  | Whether this engine has a method or property, and what it takes | \`editor_classes info\` with \`member\`, which answers that one member and which class declares it rather than the whole class |
43765
44000
  | Wait for a run to finish | \`editor_run wait\`, never a sleep or a shell loop on the pid: it answers as \`editor_output\` does, once the run is over |
43766
44001
  | End a bench and the workers it opened | \`editor_run stop\` with \`andChildren\`, never a process listing by scene name: it ends every process under the run's, to any depth, that is a game of the project, announced as one or the project's engine run with \`--path\` on it, names them under \`endedChildren\` with what identified each, and leaves and names any other |
44002
+ | End a game of the project that no server is holding | \`editor_run stop\` with its \`pid\` from \`editor_status\` \`runtimes\`, never a process kill by hand: it is ended only when it announced itself for the project and its command line is the project's engine run |
43767
44003
  | Play a game without touching the player's saves | \`editor_run start\` with \`savesIn\`, a directory for this run's \`user://\`: the editor plays with its own environment, so a run given one is started by the server, and the answer says under \`savesNote\` when the platform ignores it |
43768
44004
  | A picture, for a person who asked to see one | \`runtime_capture\` |
43769
44005
 
@@ -43957,7 +44193,7 @@ class UsageError extends Refusal2 {
43957
44193
  }
43958
44194
  function projectArgument(at) {
43959
44195
  const named = args.slice(at).find((value) => !value.startsWith("--"));
43960
- const projectPath = resolve5(named ?? ".");
44196
+ const projectPath = resolve6(named ?? ".");
43961
44197
  if (!existsSync16(join24(projectPath, "project.godot"))) {
43962
44198
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
43963
44199
  }