gdharness 1.3.50 → 1.3.53

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/keeper.js CHANGED
@@ -6946,8 +6946,8 @@ var require_public_api = __commonJS(function(exports) {
6946
6946
  });
6947
6947
 
6948
6948
  // src/keeper.ts
6949
- import { closeSync as closeSync3, existsSync as existsSync3, mkdtempSync as mkdtempSync4, openSync as openSync4, readFileSync as readFileSync8, rmSync as rmSync7, writeFileSync as writeFileSync3 } from "node:fs";
6950
- import { tmpdir as tmpdir6 } from "node:os";
6949
+ import { closeSync as closeSync3, existsSync as existsSync4, mkdtempSync as mkdtempSync4, openSync as openSync4, readFileSync as readFileSync9, rmSync as rmSync7, writeFileSync as writeFileSync4 } from "node:fs";
6950
+ import { tmpdir as tmpdir8 } from "node:os";
6951
6951
  import { join as join10 } from "node:path";
6952
6952
  import { setTimeout as delay3 } from "node:timers/promises";
6953
6953
 
@@ -6976,6 +6976,10 @@ function alive(pid) {
6976
6976
  }
6977
6977
 
6978
6978
  // src/desktop.ts
6979
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
6980
+ import { tmpdir as tmpdir2 } from "node:os";
6981
+
6982
+ // src/compiled-helper.ts
6979
6983
  import { createHash } from "node:crypto";
6980
6984
  import {
6981
6985
  closeSync,
@@ -7018,6 +7022,109 @@ function runHidden(command, args, options = {}) {
7018
7022
  });
7019
7023
  }
7020
7024
 
7025
+ // src/compiled-helper.ts
7026
+ var HELPER_COMPILE_MS = 180000;
7027
+
7028
+ class CompiledHelper {
7029
+ named;
7030
+ file;
7031
+ source;
7032
+ digest;
7033
+ constructor(named, file, source) {
7034
+ this.named = named;
7035
+ this.file = file;
7036
+ this.source = source;
7037
+ this.digest = createHash("sha256").update(source).digest("hex").slice(0, 16);
7038
+ }
7039
+ assembly(directory = tmpdir()) {
7040
+ return join(directory, `${this.file}-${this.digest}.dll`);
7041
+ }
7042
+ async ensure(directory = tmpdir(), budgetMs = HELPER_COMPILE_MS, wait = true) {
7043
+ const assembly = this.assembly(directory);
7044
+ const lock = `${assembly}.compiling`;
7045
+ const until = Date.now() + budgetMs;
7046
+ while (Date.now() < until) {
7047
+ if (existsSync(assembly)) {
7048
+ return null;
7049
+ }
7050
+ let mine = false;
7051
+ try {
7052
+ const held = openSync(lock, "wx");
7053
+ writeSync(held, String(process.pid));
7054
+ closeSync(held);
7055
+ mine = true;
7056
+ } catch {}
7057
+ if (mine) {
7058
+ try {
7059
+ return await this.compile(directory, assembly, until - Date.now());
7060
+ } finally {
7061
+ rmSync(lock, { force: true });
7062
+ }
7063
+ }
7064
+ try {
7065
+ const holder = Number(readFileSync(lock, "utf8").trim());
7066
+ const old = Date.now() - statSync(lock).mtimeMs > budgetMs;
7067
+ if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
7068
+ rmSync(lock, { force: true });
7069
+ continue;
7070
+ }
7071
+ } catch {}
7072
+ if (!wait) {
7073
+ return null;
7074
+ }
7075
+ await delay(200);
7076
+ }
7077
+ return `the ${this.named} was not compiled within ${String(budgetMs)} ms`;
7078
+ }
7079
+ async compile(directory, assembly, budgetMs) {
7080
+ const scratch = mkdtempSync(join(directory, `${this.file}-source-`));
7081
+ const source = join(scratch, "helper.cs");
7082
+ writeFileSync(source, this.source, "utf8");
7083
+ const literal = (path) => `'${path.replaceAll("'", "''")}'`;
7084
+ const script = [
7085
+ "$ErrorActionPreference = 'Stop'",
7086
+ "try {",
7087
+ ` $partial = ${literal(assembly)} + '.' + $PID`,
7088
+ ` Add-Type -TypeDefinition ([IO.File]::ReadAllText(${literal(source)}, [Text.Encoding]::UTF8)) -OutputAssembly $partial`,
7089
+ ` Move-Item -LiteralPath $partial -Destination ${literal(assembly)}`,
7090
+ "} catch {",
7091
+ " [Console]::Error.WriteLine($_.Exception.Message)",
7092
+ " exit 1",
7093
+ "}"
7094
+ ].join(`
7095
+ `);
7096
+ try {
7097
+ return await new Promise((resolve) => {
7098
+ const compiler = spawnHidden("powershell.exe", [
7099
+ "-NoProfile",
7100
+ "-NonInteractive",
7101
+ "-ExecutionPolicy",
7102
+ "Bypass",
7103
+ "-EncodedCommand",
7104
+ Buffer.from(script, "utf16le").toString("base64")
7105
+ ], { stdio: ["ignore", "ignore", "pipe"] });
7106
+ let complained = "";
7107
+ compiler.stderr.on("data", (chunk) => {
7108
+ complained += chunk.toString();
7109
+ });
7110
+ const timer = setTimeout(() => {
7111
+ compiler.kill();
7112
+ }, budgetMs);
7113
+ compiler.once("error", (error) => {
7114
+ clearTimeout(timer);
7115
+ resolve(`Windows PowerShell did not start: ${error.message}`);
7116
+ });
7117
+ compiler.once("close", (code) => {
7118
+ clearTimeout(timer);
7119
+ resolve(existsSync(assembly) ? null : `Windows PowerShell could not compile the ${this.named}${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
7120
+ });
7121
+ });
7122
+ } finally {
7123
+ rmSync(scratch, { recursive: true, force: true });
7124
+ }
7125
+ }
7126
+ }
7127
+
7021
7128
  // src/desktop.ts
7022
7129
  var DESKTOP_VARIABLE = "GDHARNESS_DESKTOP";
7023
7130
  var SOURCE = `using System;
@@ -7244,94 +7351,12 @@ public static class GdharnessDesktop {
7244
7351
  }
7245
7352
  }
7246
7353
  `;
7247
- var DIGEST = createHash("sha256").update(SOURCE).digest("hex").slice(0, 16);
7248
- var HELPER_COMPILE_MS = 180000;
7249
- function helperAssembly(directory = tmpdir()) {
7250
- return join(directory, `gdharness-desktop-${DIGEST}.dll`);
7354
+ var HELPER = new CompiledHelper("desktop helper", "gdharness-desktop", SOURCE);
7355
+ function helperAssembly(directory = tmpdir2()) {
7356
+ return HELPER.assembly(directory);
7251
7357
  }
7252
- async function ensureHelperCompiled(directory = tmpdir(), budgetMs = HELPER_COMPILE_MS, wait = true) {
7253
- const assembly = helperAssembly(directory);
7254
- const lock = `${assembly}.compiling`;
7255
- const until = Date.now() + budgetMs;
7256
- while (Date.now() < until) {
7257
- if (existsSync(assembly)) {
7258
- return null;
7259
- }
7260
- let mine = false;
7261
- try {
7262
- const held = openSync(lock, "wx");
7263
- writeSync(held, String(process.pid));
7264
- closeSync(held);
7265
- mine = true;
7266
- } catch {}
7267
- if (mine) {
7268
- try {
7269
- return await compileHelper(directory, assembly, until - Date.now());
7270
- } finally {
7271
- rmSync(lock, { force: true });
7272
- }
7273
- }
7274
- try {
7275
- const holder = Number(readFileSync(lock, "utf8").trim());
7276
- const old = Date.now() - statSync(lock).mtimeMs > budgetMs;
7277
- if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
7278
- rmSync(lock, { force: true });
7279
- continue;
7280
- }
7281
- } catch {}
7282
- if (!wait) {
7283
- return null;
7284
- }
7285
- await delay(200);
7286
- }
7287
- return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
7288
- }
7289
- async function compileHelper(directory, assembly, budgetMs) {
7290
- const scratch = mkdtempSync(join(directory, "gdharness-desktop-source-"));
7291
- const source = join(scratch, "helper.cs");
7292
- writeFileSync(source, SOURCE, "utf8");
7293
- const literal = (path) => `'${path.replaceAll("'", "''")}'`;
7294
- const script = [
7295
- "$ErrorActionPreference = 'Stop'",
7296
- "try {",
7297
- ` $partial = ${literal(assembly)} + '.' + $PID`,
7298
- ` Add-Type -TypeDefinition ([IO.File]::ReadAllText(${literal(source)}, [Text.Encoding]::UTF8)) -OutputAssembly $partial`,
7299
- ` Move-Item -LiteralPath $partial -Destination ${literal(assembly)}`,
7300
- "} catch {",
7301
- " [Console]::Error.WriteLine($_.Exception.Message)",
7302
- " exit 1",
7303
- "}"
7304
- ].join(`
7305
- `);
7306
- try {
7307
- return await new Promise((resolve) => {
7308
- const compiler = spawnHidden("powershell.exe", [
7309
- "-NoProfile",
7310
- "-NonInteractive",
7311
- "-ExecutionPolicy",
7312
- "Bypass",
7313
- "-EncodedCommand",
7314
- Buffer.from(script, "utf16le").toString("base64")
7315
- ], { stdio: ["ignore", "ignore", "pipe"] });
7316
- let complained = "";
7317
- compiler.stderr.on("data", (chunk) => {
7318
- complained += chunk.toString();
7319
- });
7320
- const timer = setTimeout(() => {
7321
- compiler.kill();
7322
- }, budgetMs);
7323
- compiler.once("error", (error) => {
7324
- clearTimeout(timer);
7325
- resolve(`Windows PowerShell did not start: ${error.message}`);
7326
- });
7327
- compiler.once("close", (code) => {
7328
- clearTimeout(timer);
7329
- resolve(existsSync(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
7330
- });
7331
- });
7332
- } finally {
7333
- rmSync(scratch, { recursive: true, force: true });
7334
- }
7358
+ function ensureHelperCompiled(directory = tmpdir2(), budgetMs = HELPER_COMPILE_MS, wait = true) {
7359
+ return HELPER.ensure(directory, budgetMs, wait);
7335
7360
  }
7336
7361
  function quoted(argument) {
7337
7362
  if (argument !== "" && !/[\s"]/.test(argument)) {
@@ -7354,7 +7379,7 @@ function windowsCommandLine(command, args) {
7354
7379
  }
7355
7380
  function helperFailure(file) {
7356
7381
  try {
7357
- const said = readFileSync(file, "utf8").trim();
7382
+ const said = readFileSync2(file, "utf8").trim();
7358
7383
  return said === "" ? null : said;
7359
7384
  } catch {
7360
7385
  return null;
@@ -7363,7 +7388,7 @@ function helperFailure(file) {
7363
7388
  function helperStep(stepFile) {
7364
7389
  let step = null;
7365
7390
  try {
7366
- step = readFileSync(stepFile, "utf8").trim();
7391
+ step = readFileSync2(stepFile, "utf8").trim();
7367
7392
  } catch {}
7368
7393
  return step === null || step === "" ? "Windows PowerShell had not begun running the helper" : `the helper was ${step}`;
7369
7394
  }
@@ -7378,7 +7403,7 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7378
7403
  files.step,
7379
7404
  how.affinity === undefined ? "" : how.affinity.toString()
7380
7405
  ];
7381
- writeFileSync(files.spec, fields.map((field) => Buffer.from(field, "utf8").toString("base64")).join(`
7406
+ writeFileSync2(files.spec, fields.map((field) => Buffer.from(field, "utf8").toString("base64")).join(`
7382
7407
  `), "utf8");
7383
7408
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
7384
7409
  const script = [
@@ -7410,10 +7435,11 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
7410
7435
  // src/engine-slots.ts
7411
7436
  import {
7412
7437
  closeSync as closeSync2,
7438
+ existsSync as existsSync2,
7413
7439
  mkdirSync,
7414
7440
  openSync as openSync2,
7415
7441
  readdirSync,
7416
- readFileSync as readFileSync4,
7442
+ readFileSync as readFileSync5,
7417
7443
  rmSync as rmSync3,
7418
7444
  statSync as statSync3,
7419
7445
  watch,
@@ -7943,6 +7969,506 @@ function entryFor(shape, launch, held) {
7943
7969
  };
7944
7970
  }
7945
7971
 
7972
+ // src/log-read.ts
7973
+ var LOG_LIMIT_BYTES = 128 * 1024 * 1024;
7974
+
7975
+ // src/process-table.ts
7976
+ import { tmpdir as tmpdir4 } from "node:os";
7977
+
7978
+ // src/windows-ask.ts
7979
+ import { tmpdir as tmpdir3 } from "node:os";
7980
+ import process3 from "node:process";
7981
+ var SCRIPT = [
7982
+ "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
7983
+ "$in = [Console]::In",
7984
+ "while ($null -ne ($line = $in.ReadLine())) {",
7985
+ ' $parts = $line.Split(" ", 2)',
7986
+ " try {",
7987
+ " $asked = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($parts[1]))",
7988
+ " $said = (& ([ScriptBlock]::Create($asked)) | Out-String -Width 1000000)",
7989
+ ' $tag = "OK"',
7990
+ " } catch {",
7991
+ " $said = $_.Exception.Message",
7992
+ ' $tag = "ERR"',
7993
+ " }",
7994
+ ' [Console]::Out.Write($parts[0] + " " + $tag + " " + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($said)) + "`n")',
7995
+ " [Console]::Out.Flush()",
7996
+ "}"
7997
+ ].join(`
7998
+ `);
7999
+
8000
+ class WindowsAskFailure extends Error {
8001
+ constructor(how) {
8002
+ super(`PowerShell ${how}`);
8003
+ }
8004
+ }
8005
+ var helper = null;
8006
+ var buffered = "";
8007
+ var next = 1;
8008
+ var out = null;
8009
+ var queued = [];
8010
+ var held = false;
8011
+ function holding(child, wanted) {
8012
+ if (held === wanted) {
8013
+ return;
8014
+ }
8015
+ held = wanted;
8016
+ const stdout = child.stdout;
8017
+ if (wanted) {
8018
+ stdout.ref?.();
8019
+ } else {
8020
+ stdout.unref?.();
8021
+ }
8022
+ }
8023
+ function running() {
8024
+ if (helper?.exitCode === null && helper.signalCode === null) {
8025
+ return helper;
8026
+ }
8027
+ const child = spawnHidden("powershell.exe", [
8028
+ "-NoProfile",
8029
+ "-NonInteractive",
8030
+ "-ExecutionPolicy",
8031
+ "Bypass",
8032
+ "-EncodedCommand",
8033
+ Buffer.from(SCRIPT, "utf16le").toString("base64")
8034
+ ], { cwd: tmpdir3() });
8035
+ buffered = "";
8036
+ child.stdout.setEncoding("utf8");
8037
+ child.stdout.on("data", (chunk) => {
8038
+ buffered += chunk;
8039
+ for (let end = buffered.indexOf(`
8040
+ `);end !== -1; end = buffered.indexOf(`
8041
+ `)) {
8042
+ answered(buffered.slice(0, end).trim());
8043
+ buffered = buffered.slice(end + 1);
8044
+ }
8045
+ });
8046
+ child.stderr.resume();
8047
+ child.stdin.on("error", () => {});
8048
+ const gone = () => {
8049
+ if (helper !== child) {
8050
+ return;
8051
+ }
8052
+ helper = null;
8053
+ if (out !== null) {
8054
+ clearTimeout(out.timer);
8055
+ out.question.reject(new WindowsAskFailure("exited before it answered"));
8056
+ out = null;
8057
+ }
8058
+ sendNext();
8059
+ };
8060
+ child.once("exit", gone);
8061
+ child.once("error", gone);
8062
+ child.unref();
8063
+ child.stdin.unref?.();
8064
+ child.stderr.unref?.();
8065
+ held = true;
8066
+ holding(child, false);
8067
+ helper = child;
8068
+ return child;
8069
+ }
8070
+ function answered(line) {
8071
+ const [id, tag, body] = line.split(" ");
8072
+ if (out === null || out.id !== Number(id)) {
8073
+ return;
8074
+ }
8075
+ const { question, timer } = out;
8076
+ clearTimeout(timer);
8077
+ out = null;
8078
+ const said = Buffer.from(body ?? "", "base64").toString("utf8");
8079
+ if (tag === "OK") {
8080
+ question.resolve(said);
8081
+ } else {
8082
+ question.reject(new WindowsAskFailure(`refused the question: ${said.trim() || "no reason given"}`));
8083
+ }
8084
+ sendNext();
8085
+ }
8086
+ function sendNext() {
8087
+ const question = queued.shift();
8088
+ if (question === undefined) {
8089
+ if (helper !== null) {
8090
+ holding(helper, false);
8091
+ }
8092
+ return;
8093
+ }
8094
+ const child = running();
8095
+ holding(child, true);
8096
+ const id = next;
8097
+ next += 1;
8098
+ const timer = setTimeout(() => {
8099
+ if (out?.id !== id) {
8100
+ return;
8101
+ }
8102
+ out = null;
8103
+ question.reject(new WindowsAskFailure(`did not answer within ${question.withinMs}ms`));
8104
+ if (helper === child) {
8105
+ helper = null;
8106
+ }
8107
+ child.kill();
8108
+ sendNext();
8109
+ }, question.withinMs);
8110
+ out = { id, question, timer };
8111
+ child.stdin.write(`${id} ${Buffer.from(question.script, "utf8").toString("base64")}
8112
+ `);
8113
+ }
8114
+ function askWindows(script, withinMs) {
8115
+ if (process3.platform !== "win32") {
8116
+ return Promise.reject(new WindowsAskFailure("is asked only on Windows"));
8117
+ }
8118
+ return new Promise((resolve, reject) => {
8119
+ queued.push({ script, withinMs, resolve, reject });
8120
+ if (out === null) {
8121
+ sendNext();
8122
+ }
8123
+ });
8124
+ }
8125
+
8126
+ // src/process-table.ts
8127
+ var SOURCE2 = String.raw`
8128
+ using System;
8129
+ using System.Collections.Generic;
8130
+ using System.Globalization;
8131
+ using System.Runtime.InteropServices;
8132
+ using System.Text;
8133
+
8134
+ public static class GdhProcessTable
8135
+ {
8136
+ [DllImport("ntdll.dll")]
8137
+ private static extern int NtQuerySystemInformation(int infoClass, IntPtr info, int length, out int needed);
8138
+
8139
+ [DllImport("ntdll.dll")]
8140
+ private static extern int NtQueryInformationProcess(IntPtr process, int infoClass, IntPtr info, int length, out int needed);
8141
+
8142
+ [DllImport("kernel32.dll")]
8143
+ private static extern IntPtr OpenProcess(int access, bool inherit, int pid);
8144
+
8145
+ [DllImport("kernel32.dll")]
8146
+ private static extern bool CloseHandle(IntPtr handle);
8147
+
8148
+ [DllImport("kernel32.dll")]
8149
+ private static extern bool GetProcessTimes(IntPtr process, out long created, out long exited, out long kernel, out long user);
8150
+
8151
+ [DllImport("kernel32.dll")]
8152
+ private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
8153
+
8154
+ [DllImport("kernel32.dll")]
8155
+ private static extern bool GetExitCodeProcess(IntPtr process, out int code);
8156
+
8157
+ [StructLayout(LayoutKind.Sequential)]
8158
+ private struct MemoryStatus
8159
+ {
8160
+ public uint Length;
8161
+ public uint MemoryLoad;
8162
+ public ulong TotalPhys;
8163
+ public ulong AvailPhys;
8164
+ public ulong TotalPageFile;
8165
+ public ulong AvailPageFile;
8166
+ public ulong TotalVirtual;
8167
+ public ulong AvailVirtual;
8168
+ public ulong AvailExtendedVirtual;
8169
+ }
8170
+
8171
+ [DllImport("kernel32.dll")]
8172
+ private static extern bool GlobalMemoryStatusEx(ref MemoryStatus status);
8173
+
8174
+ private const int SystemProcessInformation = 5;
8175
+ private const int ProcessCommandLineInformation = 60;
8176
+ private const int QueryLimitedInformation = 0x1000;
8177
+ private const int Synchronize = 0x100000;
8178
+ private const int InfoLengthMismatch = unchecked((int)0xC0000004);
8179
+ private const int BufferTooSmall = unchecked((int)0xC0000023);
8180
+ private const int BufferOverflow = unchecked((int)0x80000005);
8181
+ private const long UnixEpoch = 116444736000000000L;
8182
+
8183
+ private struct Entry
8184
+ {
8185
+ public int Pid;
8186
+ public int Parent;
8187
+ public long Created;
8188
+ public long Processor;
8189
+ }
8190
+
8191
+ private static readonly Dictionary<int, IntPtr> held = new Dictionary<int, IntPtr>();
8192
+
8193
+ // The offsets of SYSTEM_PROCESS_INFORMATION's fields, which move with the pointer size after
8194
+ // ImageName, a UNICODE_STRING.
8195
+ private static List<Entry> Snapshot()
8196
+ {
8197
+ int pidAt = IntPtr.Size == 8 ? 80 : 68;
8198
+ int parentAt = IntPtr.Size == 8 ? 88 : 72;
8199
+ int length = 1 << 20;
8200
+ while (true)
8201
+ {
8202
+ IntPtr buffer = Marshal.AllocHGlobal(length);
8203
+ try
8204
+ {
8205
+ int needed;
8206
+ int status = NtQuerySystemInformation(SystemProcessInformation, buffer, length, out needed);
8207
+ if (status == InfoLengthMismatch)
8208
+ {
8209
+ // Room for the processes started between this call and the next.
8210
+ length = Math.Max(length * 2, needed + (1 << 16));
8211
+ continue;
8212
+ }
8213
+ if (status != 0)
8214
+ {
8215
+ throw new InvalidOperationException("NtQuerySystemInformation answered 0x" + status.ToString("X8", CultureInfo.InvariantCulture));
8216
+ }
8217
+ List<Entry> entries = new List<Entry>();
8218
+ long offset = 0;
8219
+ while (true)
8220
+ {
8221
+ IntPtr at = new IntPtr(buffer.ToInt64() + offset);
8222
+ Entry entry = new Entry();
8223
+ entry.Created = Marshal.ReadInt64(at, 32);
8224
+ entry.Processor = Marshal.ReadInt64(at, 40) + Marshal.ReadInt64(at, 48);
8225
+ entry.Pid = (int)Marshal.ReadIntPtr(at, pidAt).ToInt64();
8226
+ entry.Parent = (int)Marshal.ReadIntPtr(at, parentAt).ToInt64();
8227
+ entries.Add(entry);
8228
+ int next = Marshal.ReadInt32(at, 0);
8229
+ if (next == 0)
8230
+ {
8231
+ return entries;
8232
+ }
8233
+ offset += next;
8234
+ }
8235
+ }
8236
+ finally
8237
+ {
8238
+ Marshal.FreeHGlobal(buffer);
8239
+ }
8240
+ }
8241
+ }
8242
+
8243
+ // Zero for the few processes the kernel does not date, the idle process and System among them.
8244
+ private static string Milliseconds(long created)
8245
+ {
8246
+ long milliseconds = created > UnixEpoch ? (created - UnixEpoch) / 10000 : 0;
8247
+ return milliseconds.ToString(CultureInfo.InvariantCulture);
8248
+ }
8249
+
8250
+ private static string Number(int value)
8251
+ {
8252
+ return value.ToString(CultureInfo.InvariantCulture);
8253
+ }
8254
+
8255
+ // The handle is to the listed process only if it started when the listing says, since a number
8256
+ // can be handed on between the listing and the open.
8257
+ private static IntPtr Open(Entry entry, int access)
8258
+ {
8259
+ IntPtr handle = OpenProcess(access, false, entry.Pid);
8260
+ if (handle == IntPtr.Zero)
8261
+ {
8262
+ return handle;
8263
+ }
8264
+ long created;
8265
+ long exited;
8266
+ long kernel;
8267
+ long user;
8268
+ if (!GetProcessTimes(handle, out created, out exited, out kernel, out user) || created != entry.Created)
8269
+ {
8270
+ CloseHandle(handle);
8271
+ return IntPtr.Zero;
8272
+ }
8273
+ return handle;
8274
+ }
8275
+
8276
+ // Empty where the process will not be opened, which for another user's process is the usual
8277
+ // answer. On one line, so that a line break in it cannot pass for a process; a few system
8278
+ // processes carry a NUL in theirs.
8279
+ private static string CommandLineOf(IntPtr process)
8280
+ {
8281
+ int length = 4096;
8282
+ for (int tries = 0; tries < 4; tries++)
8283
+ {
8284
+ IntPtr buffer = Marshal.AllocHGlobal(length);
8285
+ try
8286
+ {
8287
+ int needed;
8288
+ int status = NtQueryInformationProcess(process, ProcessCommandLineInformation, buffer, length, out needed);
8289
+ if (status == InfoLengthMismatch || status == BufferTooSmall || status == BufferOverflow)
8290
+ {
8291
+ length = Math.Max(needed, length * 2);
8292
+ continue;
8293
+ }
8294
+ if (status != 0)
8295
+ {
8296
+ return "";
8297
+ }
8298
+ int bytes = Marshal.ReadInt16(buffer, 0) & 0xFFFF;
8299
+ IntPtr text = Marshal.ReadIntPtr(buffer, IntPtr.Size);
8300
+ if (bytes == 0 || text == IntPtr.Zero)
8301
+ {
8302
+ return "";
8303
+ }
8304
+ return Marshal.PtrToStringUni(text, bytes / 2).Replace('\r', ' ').Replace('\n', ' ').Replace('\0', ' ');
8305
+ }
8306
+ finally
8307
+ {
8308
+ Marshal.FreeHGlobal(buffer);
8309
+ }
8310
+ }
8311
+ return "";
8312
+ }
8313
+
8314
+ private static string CommandLineOf(Entry entry)
8315
+ {
8316
+ IntPtr handle = Open(entry, QueryLimitedInformation);
8317
+ if (handle == IntPtr.Zero)
8318
+ {
8319
+ return "";
8320
+ }
8321
+ try
8322
+ {
8323
+ return CommandLineOf(handle);
8324
+ }
8325
+ finally
8326
+ {
8327
+ CloseHandle(handle);
8328
+ }
8329
+ }
8330
+
8331
+ private static HashSet<int> Pids(string listed)
8332
+ {
8333
+ HashSet<int> pids = new HashSet<int>();
8334
+ foreach (string one in listed.Split(','))
8335
+ {
8336
+ int pid;
8337
+ if (int.TryParse(one.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out pid))
8338
+ {
8339
+ pids.Add(pid);
8340
+ }
8341
+ }
8342
+ return pids;
8343
+ }
8344
+
8345
+ /// <summary>Every process as "pid parent started command", one a line.</summary>
8346
+ public static string Table()
8347
+ {
8348
+ StringBuilder said = new StringBuilder();
8349
+ foreach (Entry entry in Snapshot())
8350
+ {
8351
+ if (entry.Pid <= 0)
8352
+ {
8353
+ continue;
8354
+ }
8355
+ said.Append(Number(entry.Pid)).Append(' ').Append(Number(entry.Parent)).Append(' ')
8356
+ .Append(Milliseconds(entry.Created)).Append(' ').Append(CommandLineOf(entry)).Append('\n');
8357
+ }
8358
+ return said.ToString();
8359
+ }
8360
+
8361
+ /// <summary>"pid started" for each of the comma-separated pids the kernel dates.</summary>
8362
+ public static string Starts(string pids)
8363
+ {
8364
+ HashSet<int> wanted = Pids(pids);
8365
+ StringBuilder said = new StringBuilder();
8366
+ foreach (Entry entry in Snapshot())
8367
+ {
8368
+ if (wanted.Contains(entry.Pid) && entry.Created > UnixEpoch)
8369
+ {
8370
+ said.Append(Number(entry.Pid)).Append(' ').Append(Milliseconds(entry.Created)).Append('\n');
8371
+ }
8372
+ }
8373
+ return said.ToString();
8374
+ }
8375
+
8376
+ /// <summary>When the pid started and, on the next line, its command line; empty when it is not running.</summary>
8377
+ public static string One(int pid)
8378
+ {
8379
+ foreach (Entry entry in Snapshot())
8380
+ {
8381
+ if (entry.Pid == pid)
8382
+ {
8383
+ return Milliseconds(entry.Created) + "\n" + CommandLineOf(entry);
8384
+ }
8385
+ }
8386
+ return "";
8387
+ }
8388
+
8389
+ /// <summary>The processor seconds the pid has used; empty when it is not running.</summary>
8390
+ public static string ProcessorSeconds(int pid)
8391
+ {
8392
+ foreach (Entry entry in Snapshot())
8393
+ {
8394
+ if (entry.Pid == pid)
8395
+ {
8396
+ return (entry.Processor / 1e7).ToString("R", CultureInfo.InvariantCulture);
8397
+ }
8398
+ }
8399
+ return "";
8400
+ }
8401
+
8402
+ /// <summary>How many more bytes Windows will commit.</summary>
8403
+ public static string CommitLeft()
8404
+ {
8405
+ MemoryStatus status = new MemoryStatus();
8406
+ status.Length = (uint)Marshal.SizeOf(typeof(MemoryStatus));
8407
+ if (!GlobalMemoryStatusEx(ref status))
8408
+ {
8409
+ throw new InvalidOperationException("GlobalMemoryStatusEx failed with " + Number(Marshal.GetLastWin32Error()));
8410
+ }
8411
+ return status.AvailPageFile.ToString(CultureInfo.InvariantCulture);
8412
+ }
8413
+
8414
+ /// <summary>
8415
+ /// "pid started command" for each dated child of the pid, holding a handle to each: Windows tells
8416
+ /// a process's exit code only to whoever holds a handle to it, and the handle keeps its number
8417
+ /// from going to anything else.
8418
+ /// </summary>
8419
+ public static string HoldChildren(int parent)
8420
+ {
8421
+ StringBuilder said = new StringBuilder();
8422
+ foreach (Entry entry in Snapshot())
8423
+ {
8424
+ if (entry.Parent != parent || entry.Pid <= 0 || entry.Created <= UnixEpoch || held.ContainsKey(entry.Pid))
8425
+ {
8426
+ continue;
8427
+ }
8428
+ IntPtr handle = Open(entry, QueryLimitedInformation | Synchronize);
8429
+ if (handle == IntPtr.Zero)
8430
+ {
8431
+ continue;
8432
+ }
8433
+ held[entry.Pid] = handle;
8434
+ said.Append(Number(entry.Pid)).Append(' ').Append(Milliseconds(entry.Created)).Append(' ')
8435
+ .Append(CommandLineOf(handle)).Append('\n');
8436
+ }
8437
+ return said.ToString();
8438
+ }
8439
+
8440
+ /// <summary>"exit code" once a child held by HoldChildren has ended; empty for one not held.</summary>
8441
+ public static string WaitFor(int pid)
8442
+ {
8443
+ IntPtr handle;
8444
+ if (!held.TryGetValue(pid, out handle))
8445
+ {
8446
+ return "";
8447
+ }
8448
+ WaitForSingleObject(handle, 0xFFFFFFFF);
8449
+ int code;
8450
+ return GetExitCodeProcess(handle, out code) ? "exit " + Number(code) : "";
8451
+ }
8452
+ }
8453
+ `;
8454
+ var TABLE = new CompiledHelper("process table", "gdharness-processes", SOURCE2);
8455
+
8456
+ class ProcessTableUnbuilt extends Error {
8457
+ }
8458
+ function ensureProcessTable(directory = tmpdir4(), wait = true) {
8459
+ return TABLE.ensure(directory, HELPER_COMPILE_MS, wait);
8460
+ }
8461
+ async function loadingTheTable() {
8462
+ const failed = await ensureProcessTable();
8463
+ if (failed !== null) {
8464
+ throw new ProcessTableUnbuilt(failed);
8465
+ }
8466
+ return `$null = [Reflection.Assembly]::LoadFrom('${TABLE.assembly().replaceAll("'", "''")}')`;
8467
+ }
8468
+ async function askProcessTable(call, withinMs) {
8469
+ return askWindows(`${await loadingTheTable()}; [GdhProcessTable]::${call}`, withinMs);
8470
+ }
8471
+
7946
8472
  // src/gdscript-source.ts
7947
8473
  var DECLARING = new Set(["var", "const", "func", "signal", "enum", "class", "for", "class_name"]);
7948
8474
  var NAME = "[\\p{L}_][\\p{L}\\p{N}_]*";
@@ -8001,7 +8527,7 @@ var KEYWORDS = new Set([
8001
8527
  ]);
8002
8528
 
8003
8529
  // src/whole-file.ts
8004
- import { readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
8530
+ import { readFileSync as readFileSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
8005
8531
  var HELD_FILE_MS = 1000;
8006
8532
  var HELD_FILE_RETRY_MS = 10;
8007
8533
  var HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
@@ -8009,7 +8535,7 @@ function readWhole(path) {
8009
8535
  const until = Date.now() + HELD_FILE_MS;
8010
8536
  for (;; ) {
8011
8537
  try {
8012
- return readFileSync2(path, "utf8");
8538
+ return readFileSync3(path, "utf8");
8013
8539
  } catch (error) {
8014
8540
  const code = error.code ?? "";
8015
8541
  if (code === "ENOENT") {
@@ -8025,7 +8551,7 @@ function readWhole(path) {
8025
8551
  function writeWhole(path, text) {
8026
8552
  const partial = `${path}.${process.pid}.partial`;
8027
8553
  try {
8028
- writeFileSync2(partial, text, "utf8");
8554
+ writeFileSync3(partial, text, "utf8");
8029
8555
  } catch {
8030
8556
  return false;
8031
8557
  }
@@ -8109,161 +8635,14 @@ var QUEUE_OVERFLOW_FRAMES = new Map([
8109
8635
  ["ed1daf0bf001b61586d9930840f2f1394092c079", ["main+3e1082a", "main+3e0f4aa"]]
8110
8636
  ]);
8111
8637
 
8112
- // src/windows-ask.ts
8113
- import { tmpdir as tmpdir2 } from "node:os";
8114
- import process3 from "node:process";
8115
- var SCRIPT = [
8116
- "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
8117
- "$in = [Console]::In",
8118
- "while ($null -ne ($line = $in.ReadLine())) {",
8119
- ' $parts = $line.Split(" ", 2)',
8120
- " try {",
8121
- " $asked = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($parts[1]))",
8122
- " $said = (& ([ScriptBlock]::Create($asked)) | Out-String -Width 1000000)",
8123
- ' $tag = "OK"',
8124
- " } catch {",
8125
- " $said = $_.Exception.Message",
8126
- ' $tag = "ERR"',
8127
- " }",
8128
- ' [Console]::Out.Write($parts[0] + " " + $tag + " " + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($said)) + "`n")',
8129
- " [Console]::Out.Flush()",
8130
- "}"
8131
- ].join(`
8132
- `);
8133
-
8134
- class WindowsAskFailure extends Error {
8135
- constructor(how) {
8136
- super(`PowerShell ${how}`);
8137
- }
8138
- }
8139
- var helper = null;
8140
- var buffered = "";
8141
- var next = 1;
8142
- var out = null;
8143
- var queued = [];
8144
- var held = false;
8145
- function holding(child, wanted) {
8146
- if (held === wanted) {
8147
- return;
8148
- }
8149
- held = wanted;
8150
- const stdout = child.stdout;
8151
- if (wanted) {
8152
- stdout.ref?.();
8153
- } else {
8154
- stdout.unref?.();
8155
- }
8156
- }
8157
- function running() {
8158
- if (helper?.exitCode === null && helper.signalCode === null) {
8159
- return helper;
8160
- }
8161
- const child = spawnHidden("powershell.exe", [
8162
- "-NoProfile",
8163
- "-NonInteractive",
8164
- "-ExecutionPolicy",
8165
- "Bypass",
8166
- "-EncodedCommand",
8167
- Buffer.from(SCRIPT, "utf16le").toString("base64")
8168
- ], { cwd: tmpdir2() });
8169
- buffered = "";
8170
- child.stdout.setEncoding("utf8");
8171
- child.stdout.on("data", (chunk) => {
8172
- buffered += chunk;
8173
- for (let end = buffered.indexOf(`
8174
- `);end !== -1; end = buffered.indexOf(`
8175
- `)) {
8176
- answered(buffered.slice(0, end).trim());
8177
- buffered = buffered.slice(end + 1);
8178
- }
8179
- });
8180
- child.stderr.resume();
8181
- child.stdin.on("error", () => {});
8182
- const gone = () => {
8183
- if (helper !== child) {
8184
- return;
8185
- }
8186
- helper = null;
8187
- if (out !== null) {
8188
- clearTimeout(out.timer);
8189
- out.question.reject(new WindowsAskFailure("exited before it answered"));
8190
- out = null;
8191
- }
8192
- sendNext();
8193
- };
8194
- child.once("exit", gone);
8195
- child.once("error", gone);
8196
- child.unref();
8197
- child.stdin.unref?.();
8198
- child.stderr.unref?.();
8199
- held = true;
8200
- holding(child, false);
8201
- helper = child;
8202
- return child;
8203
- }
8204
- function answered(line) {
8205
- const [id, tag, body] = line.split(" ");
8206
- if (out === null || out.id !== Number(id)) {
8207
- return;
8208
- }
8209
- const { question, timer } = out;
8210
- clearTimeout(timer);
8211
- out = null;
8212
- const said = Buffer.from(body ?? "", "base64").toString("utf8");
8213
- if (tag === "OK") {
8214
- question.resolve(said);
8215
- } else {
8216
- question.reject(new WindowsAskFailure(`refused the question: ${said.trim() || "no reason given"}`));
8217
- }
8218
- sendNext();
8219
- }
8220
- function sendNext() {
8221
- const question = queued.shift();
8222
- if (question === undefined) {
8223
- if (helper !== null) {
8224
- holding(helper, false);
8225
- }
8226
- return;
8227
- }
8228
- const child = running();
8229
- holding(child, true);
8230
- const id = next;
8231
- next += 1;
8232
- const timer = setTimeout(() => {
8233
- if (out?.id !== id) {
8234
- return;
8235
- }
8236
- out = null;
8237
- question.reject(new WindowsAskFailure(`did not answer within ${question.withinMs}ms`));
8238
- if (helper === child) {
8239
- helper = null;
8240
- }
8241
- child.kill();
8242
- sendNext();
8243
- }, question.withinMs);
8244
- out = { id, question, timer };
8245
- child.stdin.write(`${id} ${Buffer.from(question.script, "utf8").toString("base64")}
8246
- `);
8247
- }
8248
- function askWindows(script, withinMs) {
8249
- if (process3.platform !== "win32") {
8250
- return Promise.reject(new WindowsAskFailure("is asked only on Windows"));
8251
- }
8252
- return new Promise((resolve, reject) => {
8253
- queued.push({ script, withinMs, resolve, reject });
8254
- if (out === null) {
8255
- sendNext();
8256
- }
8257
- });
8258
- }
8259
-
8260
8638
  // src/engine-launch.ts
8261
8639
  var CONTROL_SEQUENCE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
8262
8640
  var MEMORY_KEPT = 4 * 1024 ** 3;
8263
8641
  var MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
8642
+ var SCAN_BYTES = 1024 * 1024;
8264
8643
 
8265
8644
  // src/process-children.ts
8266
- import { readFileSync as readFileSync3 } from "node:fs";
8645
+ import { readFileSync as readFileSync4 } from "node:fs";
8267
8646
  import { basename as basename3 } from "node:path";
8268
8647
  async function startTimesOf(pids) {
8269
8648
  const began = new Map;
@@ -8272,8 +8651,7 @@ async function startTimesOf(pids) {
8272
8651
  }
8273
8652
  try {
8274
8653
  if (process.platform === "win32") {
8275
- const filter = pids.map((pid) => `ProcessId=${pid}`).join(" OR ");
8276
- const said = await askWindows(`Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId) $(([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds())" }`, ASK_TIMEOUT_MS);
8654
+ const said = await askProcessTable(`Starts('${pids.join(",")}')`, ASK_TIMEOUT_MS);
8277
8655
  for (const line of said.split(`
8278
8656
  `)) {
8279
8657
  const found = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
@@ -8306,7 +8684,7 @@ async function linuxStartTimes(pids) {
8306
8684
  const hertz = await ticksPerSecond();
8307
8685
  let booted;
8308
8686
  try {
8309
- booted = Number(/^btime\s+(\d+)$/m.exec(readFileSync3("/proc/stat", "utf8"))?.[1] ?? Number.NaN) * 1000;
8687
+ booted = Number(/^btime\s+(\d+)$/m.exec(readFileSync4("/proc/stat", "utf8"))?.[1] ?? Number.NaN) * 1000;
8310
8688
  } catch {
8311
8689
  return began;
8312
8690
  }
@@ -8316,7 +8694,7 @@ async function linuxStartTimes(pids) {
8316
8694
  for (const pid of pids) {
8317
8695
  let stat;
8318
8696
  try {
8319
- stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
8697
+ stat = readFileSync4(`/proc/${String(pid)}/stat`, "utf8");
8320
8698
  } catch {
8321
8699
  continue;
8322
8700
  }
@@ -8453,7 +8831,7 @@ function handSlot(lock, from, to) {
8453
8831
  }
8454
8832
  function giveBackSlot(lock, pid) {
8455
8833
  try {
8456
- if (holderOf(readFileSync4(lock, "utf8"))?.pid === pid) {
8834
+ if (holderOf(readFileSync5(lock, "utf8"))?.pid === pid) {
8457
8835
  removeWhole(lock);
8458
8836
  }
8459
8837
  } catch {}
@@ -8487,7 +8865,7 @@ function crashesReported() {
8487
8865
  }
8488
8866
 
8489
8867
  // src/game-debugger.ts
8490
- import { readFileSync as readFileSync6 } from "node:fs";
8868
+ import { readFileSync as readFileSync7 } from "node:fs";
8491
8869
  import { createServer } from "node:net";
8492
8870
  import { join as join6 } from "node:path";
8493
8871
 
@@ -8909,8 +9287,8 @@ var IMPORTABLE = new Set(IMPORTABLE_EXTENSIONS);
8909
9287
  var hashes = new Map;
8910
9288
 
8911
9289
  // src/scratch.ts
8912
- import { existsSync as existsSync2, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as rmSync4 } from "node:fs";
8913
- import { tmpdir as tmpdir3 } from "node:os";
9290
+ import { existsSync as existsSync3, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, rmSync as rmSync4 } from "node:fs";
9291
+ import { tmpdir as tmpdir5 } from "node:os";
8914
9292
  import { join as join4 } from "node:path";
8915
9293
  import process6 from "node:process";
8916
9294
  var SWEPT = { recursive: true, force: true };
@@ -8918,7 +9296,7 @@ var AGAIN_AFTER_MS = [250, 500, 1000, 2000, 4000, 8000, 16000];
8918
9296
  var waiting = new Set;
8919
9297
  var atExit = new Set;
8920
9298
  function scratchDirectory(kind) {
8921
- return mkdtempSync2(join4(tmpdir3(), `gdharness-${kind}-${process6.pid}-`));
9299
+ return mkdtempSync2(join4(tmpdir5(), `gdharness-${kind}-${process6.pid}-`));
8922
9300
  }
8923
9301
  function discardWith(remove, paths) {
8924
9302
  const left = [];
@@ -8954,7 +9332,7 @@ function tryAgain(path, attempt) {
8954
9332
  lastTryOnExit();
8955
9333
  }
8956
9334
  const after = AGAIN_AFTER_MS[attempt];
8957
- if (after === undefined || !existsSync2(path)) {
9335
+ if (after === undefined || !existsSync3(path)) {
8958
9336
  waiting.delete(path);
8959
9337
  if (after === undefined) {
8960
9338
  atExit.add(path);
@@ -9103,7 +9481,7 @@ function parseIniLikeValue(value) {
9103
9481
  }
9104
9482
 
9105
9483
  // src/server-version.ts
9106
- import { readFileSync as readFileSync5 } from "node:fs";
9484
+ import { readFileSync as readFileSync6 } from "node:fs";
9107
9485
 
9108
9486
  // src/update-check.ts
9109
9487
  var CACHE_MS = 10 * 60 * 1000;
@@ -9114,7 +9492,7 @@ var MAX_RETRY_MS = 60 * 60 * 1000;
9114
9492
  var DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
9115
9493
  var SERVER_VERSION = (() => {
9116
9494
  try {
9117
- const pkg = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8"));
9495
+ const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
9118
9496
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
9119
9497
  } catch {
9120
9498
  return "0.0.0";
@@ -9140,7 +9518,7 @@ var RUNTIME_AUTOLOAD2 = `${RUNTIME_ADDON}runtime_autoload.gd`;
9140
9518
  var frameEnd = null;
9141
9519
  function frameEndLine() {
9142
9520
  if (frameEnd === null) {
9143
- const lines = readFileSync6(join6(shippedAddonsDirectory(), "gdharness_runtime", "runtime_autoload.gd"), "utf8").split(`
9521
+ const lines = readFileSync7(join6(shippedAddonsDirectory(), "gdharness_runtime", "runtime_autoload.gd"), "utf8").split(`
9144
9522
  `);
9145
9523
  const declared = lines.findIndex((line) => line.startsWith("func _notification("));
9146
9524
  if (declared === -1) {
@@ -9794,7 +10172,7 @@ class GameDebugger {
9794
10172
  import { randomUUID } from "node:crypto";
9795
10173
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync5 } from "node:fs";
9796
10174
  import { connect, createServer as createServer2 } from "node:net";
9797
- import { tmpdir as tmpdir4 } from "node:os";
10175
+ import { tmpdir as tmpdir6 } from "node:os";
9798
10176
  import { join as join7 } from "node:path";
9799
10177
  import process7 from "node:process";
9800
10178
  var DEBUG_PREFIX = "debug ";
@@ -9823,7 +10201,7 @@ function errorText(error) {
9823
10201
  async function listenForTheServer(end, debug) {
9824
10202
  let directory = null;
9825
10203
  try {
9826
- directory = process7.platform === "win32" ? null : mkdtempSync3(join7(tmpdir4(), "gdharness-keeper-"));
10204
+ directory = process7.platform === "win32" ? null : mkdtempSync3(join7(tmpdir6(), "gdharness-keeper-"));
9827
10205
  const address = directory === null ? `\\\\.\\pipe\\gdharness-keeper-${randomUUID()}` : join7(directory, "keeper.sock");
9828
10206
  const server = createServer2((socket) => {
9829
10207
  socket.setEncoding("utf8");
@@ -9917,11 +10295,11 @@ function readLaunched(printed) {
9917
10295
 
9918
10296
  // src/run-record.ts
9919
10297
  import { createHash as createHash2 } from "node:crypto";
9920
- import { mkdirSync as mkdirSync2, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
10298
+ import { mkdirSync as mkdirSync2, openSync as openSync3, readdirSync as readdirSync3, readFileSync as readFileSync8, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
9921
10299
  import { basename as basename4, join as join9, resolve as resolve4 } from "node:path";
9922
10300
 
9923
10301
  // src/runtime-client.ts
9924
- import { tmpdir as tmpdir5 } from "node:os";
10302
+ import { tmpdir as tmpdir7 } from "node:os";
9925
10303
  import { dirname as dirname4, join as join8, resolve as resolve3 } from "node:path";
9926
10304
 
9927
10305
  // src/launch.ts
@@ -9967,7 +10345,7 @@ function runtimeDirectory(variables = process.env) {
9967
10345
  return explicit;
9968
10346
  }
9969
10347
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
9970
- return join8(perUser ?? tmpdir5(), "gdharness");
10348
+ return join8(perUser ?? tmpdir7(), "gdharness");
9971
10349
  }
9972
10350
  var ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
9973
10351
  var verdicts = new Map;
@@ -10013,7 +10391,7 @@ function recordRunEnded(projectPath, pid, ending) {
10013
10391
  function recordAt(path) {
10014
10392
  let parsed;
10015
10393
  try {
10016
- parsed = JSON.parse(readFileSync7(path, "utf8"));
10394
+ parsed = JSON.parse(readFileSync8(path, "utf8"));
10017
10395
  } catch {
10018
10396
  return null;
10019
10397
  }
@@ -10258,21 +10636,19 @@ async function followSuccessor(exitedPid, engine, notBefore, exitedAt, transcrip
10258
10636
  return transcript === undefined ? null : await followOnPosix(exitedPid, engine, notBefore, exitedAt, transcript);
10259
10637
  }
10260
10638
  async function followOnWindows(exitedPid, engine, notBefore, exitedAt) {
10639
+ let loading;
10640
+ try {
10641
+ loading = await loadingTheTable();
10642
+ } catch {
10643
+ return null;
10644
+ }
10261
10645
  const script = [
10262
10646
  "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
10263
- "$held = @{}",
10264
- `foreach ($c in @(Get-CimInstance Win32_Process -Filter "ParentProcessId=${exitedPid}" -Property ProcessId,CreationDate,CommandLine)) {`,
10265
- " if (-not $c.CreationDate) { continue }",
10266
- " $began = ([DateTimeOffset]$c.CreationDate).ToUnixTimeMilliseconds()",
10267
- " $p = Get-Process -Id $c.ProcessId -ErrorAction SilentlyContinue",
10268
- " if ($null -eq $p -or [Math]::Abs(([DateTimeOffset]$p.StartTime).ToUnixTimeMilliseconds() - $began) -gt 1000) { continue }",
10269
- " $null = $p.Handle",
10270
- " $held[[int]$c.ProcessId] = $p",
10271
- ' "$($c.ProcessId) $began $($c.CommandLine)"',
10272
- "}",
10647
+ loading,
10648
+ `[Console]::Out.Write([GdhProcessTable]::HoldChildren(${exitedPid}))`,
10273
10649
  "'listed'",
10274
10650
  "$chosen = [Console]::In.ReadLine()",
10275
- "if ($chosen -and $held.ContainsKey([int]$chosen)) { $p = $held[[int]$chosen]; $p.WaitForExit(); 'exit ' + $p.ExitCode }"
10651
+ "if ($chosen) { [GdhProcessTable]::WaitFor([int]$chosen) }"
10276
10652
  ].join(`
10277
10653
  `);
10278
10654
  const holder = spawnHidden("powershell.exe", [
@@ -10452,7 +10828,7 @@ async function startThroughHelper(spec, output, stopFile) {
10452
10828
  step: stepFile
10453
10829
  });
10454
10830
  const envChanges = spec.desktop === undefined ? spec.envChanges : { ...spec.envChanges, [DESKTOP_VARIABLE]: spec.desktop };
10455
- const started = await startDirectly({ ...spec, command: wrapper.command, args: wrapper.args, envChanges }, output, true, false, "ignore", tmpdir6());
10831
+ const started = await startDirectly({ ...spec, command: wrapper.command, args: wrapper.args, envChanges }, output, true, false, "ignore", tmpdir8());
10456
10832
  if ("error" in started) {
10457
10833
  return started;
10458
10834
  }
@@ -10461,7 +10837,7 @@ async function startThroughHelper(spec, output, stopFile) {
10461
10837
  exited.now = true;
10462
10838
  });
10463
10839
  for (let waited = 0;waited < DESKTOP_START_MS; waited += 50) {
10464
- const pid = existsSync3(pidFile) ? Number(readFileSync8(pidFile, "utf8").trim()) : Number.NaN;
10840
+ const pid = existsSync4(pidFile) ? Number(readFileSync9(pidFile, "utf8").trim()) : Number.NaN;
10465
10841
  if (Number.isInteger(pid) && pid > 0) {
10466
10842
  return { child: started.child, pid };
10467
10843
  }
@@ -10492,7 +10868,7 @@ function profiledBy(path, projectPath) {
10492
10868
  };
10493
10869
  let projectGodot = null;
10494
10870
  try {
10495
- projectGodot = readFileSync8(join10(projectPath, "project.godot"), "utf8");
10871
+ projectGodot = readFileSync9(join10(projectPath, "project.godot"), "utf8");
10496
10872
  } catch {}
10497
10873
  const observer = new ProfileObserver((totals) => {
10498
10874
  const first = latest === null;
@@ -10593,11 +10969,11 @@ async function keep() {
10593
10969
  }
10594
10970
  const profiled = run?.profile === undefined ? null : profiledBy(run.profile, run.projectPath);
10595
10971
  const debuggerPeer = run === undefined || run.debugged === false ? null : await GameDebugger.open(run.breakpoints ?? [], profiled === null ? [] : [profiled.observer]);
10596
- const exitNotes = run === undefined ? null : mkdtempSync4(join10(tmpdir6(), "gdharness-exit-"));
10972
+ const exitNotes = run === undefined ? null : mkdtempSync4(join10(tmpdir8(), "gdharness-exit-"));
10597
10973
  const noted = exitNotes === null ? spec : { ...spec, envChanges: { ...spec.envChanges, [EXIT_NOTES_VARIABLE]: exitNotes } };
10598
10974
  const toStart = debuggerPeer === null ? noted : { ...noted, args: withEngineArguments(noted.args, debuggerPeer.engineArguments) };
10599
10975
  const transcript = run === undefined ? "ignore" : openSync4(run.transcript, "a");
10600
- const own = run !== undefined && throughTheHelper(spec) ? mkdtempSync4(join10(tmpdir6(), "gdharness-stop-")) : null;
10976
+ const own = run !== undefined && throughTheHelper(spec) ? mkdtempSync4(join10(tmpdir8(), "gdharness-stop-")) : null;
10601
10977
  const stopFile = own === null ? "" : join10(own, "stop");
10602
10978
  let started;
10603
10979
  const keeperStarted = run?.slot === undefined ? undefined : ownStart();
@@ -10635,7 +11011,7 @@ async function keep() {
10635
11011
  let listening = null;
10636
11012
  let end = () => {
10637
11013
  if (stopFile !== "") {
10638
- writeFileSync3(stopFile, "");
11014
+ writeFileSync4(stopFile, "");
10639
11015
  return "signalled";
10640
11016
  }
10641
11017
  return child.kill() ? "signalled" : "gone";
@@ -10743,7 +11119,7 @@ function restartSaid(notes, pid) {
10743
11119
  return null;
10744
11120
  }
10745
11121
  try {
10746
- const said = JSON.parse(readFileSync8(join10(notes, `exit-${String(pid)}.json`), "utf8"));
11122
+ const said = JSON.parse(readFileSync9(join10(notes, `exit-${String(pid)}.json`), "utf8"));
10747
11123
  return typeof said === "object" && said !== null && "restart" in said && typeof said.restart === "boolean" ? said.restart : null;
10748
11124
  } catch {
10749
11125
  return null;