Split GraphCanvas (1076 -> 831 lines) into EdgeLayer and graphMenus; the port and marker menus were the same list and are now one function. Value specs of the inspector and the list tab share valueSpecs.ts, and the remaining copies (compound commands, type glyphs, typed asset lookup, name rule, play/editor world setup, marquee) are merged. Four copies had already drifted apart; merging them fixes: - "Disconnect N links" in the port menu is one undo step, not N - "+ Add collider" is hidden on a placed container instance, as in the hierarchy menu, instead of always failing - a bool property coerced from the text "false" becomes false - a port command on an unknown node type reports instead of crashing Comment-only edits leave the bundle byte-identical. README corrected in 35 places (step limit, jump-run clock, suite duration, 409 body, ...). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
3.5 KiB
JavaScript
118 lines
3.5 KiB
JavaScript
/**
|
|
* Launches the check browser locally, or with `GM_REMOTE=user@host` on another machine with the CDP port
|
|
* forwarded over SSH, since this 8 GB host has little memory to spare.
|
|
*/
|
|
import { spawn, execFileSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import { homedir } from 'node:os';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const LAUNCHER = join(HERE, '..', 'remote', 'gm-browser.ps1');
|
|
|
|
const FLAGS = [
|
|
'--headless',
|
|
'--hide-scrollbars',
|
|
/**
|
|
* Measured (idle editor, 20 s): software WebGL 373% of a core at 8 fps, HD 630 via ANGLE/Vulkan 69% at
|
|
* 56 fps; no software fallback on purpose. `--use-angle=gl` and `--use-gl=egl` get no WebGL context at all.
|
|
*/
|
|
'--use-angle=vulkan',
|
|
'--ignore-gpu-blocklist',
|
|
'--enable-gpu',
|
|
'--enable-features=Vulkan',
|
|
// None of this serves a headless check, and its stderr was full of GCM sign-in retries.
|
|
'--disable-background-networking',
|
|
'--disable-sync',
|
|
'--disable-extensions',
|
|
'--no-first-run',
|
|
'--mute-audio',
|
|
];
|
|
|
|
function sshArgs(key) {
|
|
return [
|
|
'-i',
|
|
key,
|
|
'-o',
|
|
'IdentitiesOnly=yes',
|
|
'-o',
|
|
'BatchMode=yes',
|
|
'-o',
|
|
'StrictHostKeyChecking=accept-new',
|
|
'-o',
|
|
'ConnectTimeout=15',
|
|
// A dead peer must surface as a closed socket, not as a run that waits forever.
|
|
'-o',
|
|
'ServerAliveInterval=10',
|
|
'-o',
|
|
'ServerAliveCountMax=3',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Starts the browser and returns its process plus a `stop()` that ends it the way that host
|
|
* needs. `proc.stderr` carries the browser's own last words either way.
|
|
*/
|
|
export function launchBrowser({ port, width, height }) {
|
|
const remote = process.env.GM_REMOTE;
|
|
if (!remote) {
|
|
const proc = spawn(
|
|
'chromium',
|
|
// --no-sandbox: this container runs as root, where chromium refuses to start otherwise.
|
|
[
|
|
...FLAGS,
|
|
'--no-sandbox',
|
|
`--remote-debugging-port=${port}`,
|
|
`--window-size=${width},${height}`,
|
|
'about:blank',
|
|
],
|
|
{ stdio: ['ignore', 'ignore', 'pipe'] },
|
|
);
|
|
return { proc, stop: () => proc.kill('SIGTERM'), where: 'local chromium' };
|
|
}
|
|
|
|
const key = process.env.GM_REMOTE_KEY ?? join(homedir(), '.ssh', 'gm-win.key');
|
|
// Copied every run so the remote never runs a stale launcher.
|
|
execFileSync('scp', [...sshArgs(key), LAUNCHER, `${remote}:gm-browser.ps1`], { stdio: 'pipe' });
|
|
|
|
const proc = spawn(
|
|
'ssh',
|
|
[
|
|
...sshArgs(key),
|
|
'-o',
|
|
'ExitOnForwardFailure=yes',
|
|
'-L',
|
|
`${port}:127.0.0.1:${port}`,
|
|
remote,
|
|
`powershell -NoProfile -ExecutionPolicy Bypass -File gm-browser.ps1 -Port ${port} -Size ${width},${height}`,
|
|
],
|
|
{ stdio: ['ignore', 'ignore', 'pipe'] },
|
|
);
|
|
|
|
return {
|
|
proc,
|
|
/**
|
|
* Ends the browser now rather than after the launcher's idle window. Kill only this pid: the host is
|
|
* someone's desktop, and killing by name would close their browser.
|
|
*/
|
|
stop: () => {
|
|
const pidFile = `$env:TEMP\\gm-browser-${port}.pid`;
|
|
try {
|
|
execFileSync(
|
|
'ssh',
|
|
[
|
|
...sshArgs(key),
|
|
remote,
|
|
`powershell -NoProfile -Command "if (Test-Path ${pidFile}) { Stop-Process -Id (Get-Content ${pidFile}) -Force -ErrorAction SilentlyContinue }"`,
|
|
],
|
|
{ stdio: 'pipe', timeout: 20_000 },
|
|
);
|
|
} catch {
|
|
// Unreachable host, denied, already gone: the idle window still collects it.
|
|
}
|
|
proc.kill('SIGTERM');
|
|
},
|
|
where: `remote browser on ${remote}`,
|
|
};
|
|
}
|