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/cli.js +11900 -10947
- package/build/godot/addons/gdharness_editor/plugin.gd +9 -0
- package/build/godot/addons/gdharness_editor/release_export.gd +66 -0
- package/build/godot/addons/gdharness_editor/release_export.gd.uid +1 -0
- package/build/godot/loader/loader.gd +3 -2
- package/build/godot/tests/listed.gd +193 -18
- package/build/keeper.js +658 -282
- package/package.json +1 -1
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
|
|
6950
|
-
import { tmpdir as
|
|
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
|
|
7248
|
-
|
|
7249
|
-
|
|
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
|
-
|
|
7253
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
8913
|
-
import { tmpdir as
|
|
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(
|
|
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 || !
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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 ??
|
|
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(
|
|
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
|
-
|
|
10264
|
-
`
|
|
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
|
|
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",
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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;
|