@gtkx/vitest 1.6.0 → 2.0.0-beta.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"headless-display.js","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveExecutable, spawnWithParentDeathSignal } from \"@gtkx/utils\";\nimport { type ChildProcess, spawnSync } from \"node:child_process\";\nimport { chmodSync, createWriteStream, existsSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { startNotificationService } from \"./notification-service.ts\";\nimport { startVirtualSeat } from \"./virtual-seat.ts\";\n\n/**\n * Wayland compositors that can back a headless display.\n */\ntype CompositorId = \"sway\" | \"weston\";\n\n/**\n * Settings for the per-worker headless Wayland display.\n */\ntype HeadlessOptions = {\n /** Output resolution as a \"WIDTHxHEIGHT\" string, for example \"1024x768\". */\n size: string;\n /** Wayland compositor used to back the headless display. */\n compositor: CompositorId;\n};\n\ntype EnvSnapshot = Record<string, string | undefined>;\n\ntype CompositorDescriptor = {\n socket: string;\n env: Record<string, string>;\n requiresVirtualSeat: boolean;\n start: (runtimeDir: string, width: string, height: string) => ChildProcess;\n};\n\ntype SpawnedCompositor = {\n child: ChildProcess;\n socket: string;\n requiresVirtualSeat: boolean;\n};\n\ntype ChildMonitor = {\n label: string;\n path: string;\n read: () => string;\n failure: () => string | undefined;\n subscribe: (notify: (failure: string) => void) => () => void;\n stop: () => void;\n};\n\ntype WaitForSocketOptions = {\n monitor: ChildMonitor;\n guards?: ChildMonitor[];\n timeout?: number;\n};\n\ntype DisplaySockets = {\n compositorMonitor: ChildMonitor;\n busMonitor: ChildMonitor;\n};\n\ntype SocketWatch = {\n options: WaitForSocketOptions;\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nconst DEFAULT_HEADLESS_SIZE = \"1024x768\";\nconst DEFAULT_HEADLESS_COMPOSITOR: CompositorId = \"sway\";\n\nconst BUS_CONFIG_DOCTYPE =\n '<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\" ' +\n '\"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">';\n\nconst hasWestonFakeSeat = createWestonFakeSeatProbe();\n\nconst compositorRegistry: Record<CompositorId, CompositorDescriptor> = {\n sway: {\n socket: \"wayland-1\",\n requiresVirtualSeat: true,\n env: {\n WLR_BACKENDS: \"headless\",\n WLR_RENDERER: \"pixman\",\n WLR_RENDERER_ALLOW_SOFTWARE: \"1\",\n WLR_LIBINPUT_NO_DEVICES: \"1\",\n WLR_HEADLESS_OUTPUTS: \"1\",\n },\n start: (runtimeDir, width, height) => {\n const configPath = join(runtimeDir, \"sway.conf\");\n\n writeFileSync(\n configPath,\n [\n \"xwayland disable\",\n \"default_border none\",\n \"default_floating_border none\",\n `output HEADLESS-1 resolution ${width}x${height}`,\n \"output HEADLESS-1 bg #000000 solid_color\",\n 'for_window [app_id=\".*\"] floating enable, border none',\n 'for_window [title=\".*\"] floating enable, border none',\n \"\",\n ].join(\"\\n\"),\n );\n\n return spawnWithParentDeathSignal(\"sway\", [\"-c\", configPath], { stdio: [\"ignore\", \"ignore\", \"pipe\"] });\n },\n },\n weston: {\n socket: \"wayland-0\",\n requiresVirtualSeat: false,\n env: {},\n start: (_runtimeDir, width, height) =>\n spawnWithParentDeathSignal(\n \"weston\",\n [\n \"--backend=headless\",\n \"--renderer=pixman\",\n ...(hasWestonFakeSeat() ? [\"--fake-seat\"] : []),\n `--width=${width}`,\n `--height=${height}`,\n \"--socket=wayland-0\",\n ],\n { stdio: [\"ignore\", \"ignore\", \"pipe\"] },\n ),\n },\n};\n\nconst STATIC_HEADLESS_ENV = {\n GDK_BACKEND: \"wayland\",\n GDK_DISABLE: \"vulkan\",\n GDK_DEBUG: \"no-vsync\",\n GSK_RENDERER: \"cairo\",\n GTK_A11Y: \"test\",\n LIBGL_ALWAYS_SOFTWARE: \"1\",\n GST_GL_WINDOW: \"none\",\n GSETTINGS_BACKEND: \"memory\",\n ALSOFT_DRIVERS: \"null\",\n ALSOFT_LOGLEVEL: \"0\",\n};\n\nconst resolveHeadlessOptions = (provided: Partial<HeadlessOptions>): HeadlessOptions => ({\n size: provided.size ?? DEFAULT_HEADLESS_SIZE,\n compositor: provided.compositor ?? DEFAULT_HEADLESS_COMPOSITOR,\n});\n\nconst applyEnv = (snapshot: EnvSnapshot, values: Record<string, string>): void => {\n for (const [name, value] of Object.entries(values)) {\n if (!Object.hasOwn(snapshot, name)) {\n snapshot[name] = process.env[name];\n }\n\n process.env[name] = value;\n }\n};\n\nconst restoreEnv = (snapshot: EnvSnapshot): void => {\n for (const [name, previous] of Object.entries(snapshot)) {\n if (previous === undefined) {\n Reflect.deleteProperty(process.env, name);\n } else {\n process.env[name] = previous;\n }\n }\n};\n\nfunction createWestonFakeSeatProbe(): () => boolean {\n let isSupported: boolean | undefined;\n\n return () => {\n if (isSupported === undefined) {\n const help = spawnSync(resolveExecutable(\"weston\"), [\"--help\"], { encoding: \"utf8\" });\n isSupported = `${help.stdout}${help.stderr}`.includes(\"--fake-seat\");\n }\n\n return isSupported;\n };\n}\n\nconst isCompositorId = (value: string): value is CompositorId => Object.hasOwn(compositorRegistry, value);\n\nconst readHeadlessOptions = (params: URLSearchParams): Partial<HeadlessOptions> => {\n const options: Partial<HeadlessOptions> = {};\n const size = params.get(\"size\");\n\n if (size !== null) {\n options.size = size;\n }\n\n const compositor = params.get(\"compositor\");\n\n if (compositor !== null && isCompositorId(compositor)) {\n options.compositor = compositor;\n }\n\n return options;\n};\n\nconst startCompositor = (runtimeDir: string, options: HeadlessOptions, env: EnvSnapshot): SpawnedCompositor => {\n const descriptor = compositorRegistry[options.compositor];\n const [width = \"\", height = \"\"] = options.size.split(\"x\", 2);\n applyEnv(env, descriptor.env);\n\n return {\n child: descriptor.start(runtimeDir, width, height),\n socket: descriptor.socket,\n requiresVirtualSeat: descriptor.requiresVirtualSeat,\n };\n};\n\nconst noVirtualSeat = (): void => undefined;\n\nconst attachVirtualSeat = (compositor: SpawnedCompositor, socketPath: string): Promise<() => void> =>\n compositor.requiresVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);\n\nconst writeBusConfig = (busConfigPath: string, busSocketPath: string): void => {\n writeFileSync(\n busConfigPath,\n [\n BUS_CONFIG_DOCTYPE,\n \"<busconfig>\",\n \" <type>session</type>\",\n ` <listen>unix:path=${busSocketPath}</listen>`,\n \" <auth>EXTERNAL</auth>\",\n ' <policy context=\"default\">',\n ' <allow send_destination=\"*\" eavesdrop=\"true\"/>',\n ' <allow eavesdrop=\"true\"/>',\n ' <allow own=\"*\"/>',\n \" </policy>\",\n \"</busconfig>\",\n ].join(\"\\n\"),\n );\n};\n\nconst exitedMessage = (label: string, path: string, code: number | null, signal: NodeJS.Signals | null): string =>\n `${label} exited (code ${String(code)}, signal ${signal ?? \"null\"}) before ${path} appeared`;\n\nconst monitorChild = (child: ChildProcess, label: string, path: string): ChildMonitor => {\n const stderr = child.stderr;\n const subscribers: Set<(failure: string) => void> = new Set();\n let log = \"\";\n let failure: string | undefined;\n\n const onData = (chunk: string): void => {\n log += chunk;\n };\n\n stderr?.setEncoding(\"utf8\");\n stderr?.on(\"data\", onData);\n\n const record = (cause: string): void => {\n failure ??= cause;\n\n for (const notify of subscribers) {\n notify(cause);\n }\n };\n\n child.on(\"close\", (code: number | null, signal: NodeJS.Signals | null) => {\n record(`${exitedMessage(label, path, code, signal)}\\n${log}`);\n });\n\n child.on(\"error\", (cause: Error) => {\n record(`${label} failed to spawn before ${path} appeared: ${cause.message}\\n${log}`);\n });\n\n return {\n label,\n path,\n read: () => log,\n failure: () => failure,\n subscribe: (notify) => {\n subscribers.add(notify);\n\n return (): void => {\n subscribers.delete(notify);\n };\n },\n stop: () => {\n stderr?.removeListener(\"data\", onData);\n stderr?.resume();\n\n if (stderr instanceof Socket) {\n stderr.unref();\n }\n },\n };\n};\n\nconst runCleanups = (cleanups: (() => void)[]): void => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n\n cleanups.length = 0;\n};\n\nconst pollForPath = (path: string, onFound: () => void): NodeJS.Timeout =>\n setInterval(() => {\n if (existsSync(path)) {\n onFound();\n }\n }, 50);\n\nconst firstFailure = (monitors: ChildMonitor[]): string | undefined => {\n for (const monitor of monitors) {\n const failure = monitor.failure();\n\n if (failure !== undefined) {\n return failure;\n }\n }\n\n return undefined;\n};\n\nconst watchForSocket = ({ options, resolve, reject }: SocketWatch): void => {\n const { monitor, guards = [], timeout = 15_000 } = options;\n const watched = [monitor, ...guards];\n const cleanups: (() => void)[] = [monitor.stop];\n\n const stop = (): void => {\n runCleanups(cleanups);\n };\n\n const fail = (message: string): void => {\n stop();\n\n for (const guard of guards) {\n guard.stop();\n }\n\n reject(new Error(message));\n };\n\n const alreadyFailed = firstFailure(watched);\n\n if (alreadyFailed !== undefined) {\n fail(alreadyFailed);\n\n return;\n }\n\n const poll = pollForPath(monitor.path, () => {\n stop();\n resolve();\n });\n\n const timer = setTimeout(() => {\n fail(`${monitor.label} did not become available within ${String(timeout)}ms\\n${monitor.read()}`);\n }, timeout);\n\n cleanups.push(\n () => {\n clearInterval(poll);\n clearTimeout(timer);\n },\n ...watched.map((entry) => entry.subscribe(fail)),\n );\n};\n\nconst waitForSocket = (options: WaitForSocketOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n watchForSocket({ options, resolve, reject });\n });\n\nconst captureCompositorStderr = (child: ChildProcess, logPath: string): string[] => {\n const captured: string[] = [];\n const stderr = child.stderr;\n\n if (stderr !== null) {\n stderr.setEncoding(\"utf8\");\n const logStream = createWriteStream(logPath);\n logStream.on(\"error\", (): void => undefined);\n\n stderr.on(\"data\", (chunk: string) => {\n captured.push(chunk);\n logStream.write(chunk);\n });\n }\n\n return captured;\n};\n\nconst compositorExitMessage = (\n code: number | null,\n signal: NodeJS.Signals | null,\n capturedStderr: string[],\n): string =>\n `[gtkx] the headless compositor exited (code ${String(code)}, signal ${signal ?? \"null\"}); ` +\n `every Wayland client in this worker has been severed.\\n${capturedStderr.join(\"\")}`;\n\nconst watchCompositorExit = (child: ChildProcess, capturedStderr: string[]): (() => void) => {\n const report = (code: number | null, signal: NodeJS.Signals | null): void => {\n process.stderr.write(compositorExitMessage(code, signal, capturedStderr));\n };\n\n child.on(\"exit\", report);\n\n return () => child.removeListener(\"exit\", report);\n};\n\nconst waitForDisplaySockets = async ({ compositorMonitor, busMonitor }: DisplaySockets): Promise<void> => {\n await waitForSocket({ monitor: busMonitor, guards: [compositorMonitor] });\n await waitForSocket({ monitor: compositorMonitor });\n};\n\nconst killSpawned = (children: ChildProcess[]): void => {\n for (const child of children) {\n child.kill(\"SIGTERM\");\n }\n};\n\nconst makeTeardown = (stops: (() => void)[]): (() => void) => {\n let isTorndown = false;\n\n return (): void => {\n if (isTorndown) {\n return;\n }\n\n isTorndown = true;\n runCleanups(stops);\n };\n};\n\nconst startHeadlessDisplay = async (options: HeadlessOptions): Promise<() => void> => {\n const env: EnvSnapshot = {};\n const runtimeDir = mkdtempSync(join(tmpdir(), \"gtkx-xdg-\"));\n chmodSync(runtimeDir, 0o700);\n const spawned: ChildProcess[] = [];\n\n const removeRuntime = (): void => {\n restoreEnv(env);\n rmSync(runtimeDir, { recursive: true, force: true });\n };\n\n try {\n applyEnv(env, { XDG_RUNTIME_DIR: runtimeDir });\n const busConfigPath = join(runtimeDir, \"session.conf\");\n const busSocketPath = join(runtimeDir, \"bus\");\n writeBusConfig(busConfigPath, busSocketPath);\n\n const busChild = spawnWithParentDeathSignal(\"dbus-daemon\", [`--config-file=${busConfigPath}`], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n\n busChild.unref();\n spawned.push(busChild);\n const busMonitor = monitorChild(busChild, \"D-Bus session bus\", busSocketPath);\n applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });\n const compositor = startCompositor(runtimeDir, options, env);\n compositor.child.unref();\n spawned.push(compositor.child);\n const compositorSocketPath = join(runtimeDir, compositor.socket);\n const compositorMonitor = monitorChild(compositor.child, \"Compositor\", compositorSocketPath);\n applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n await waitForDisplaySockets({ compositorMonitor, busMonitor });\n const stopVirtualSeat = await attachVirtualSeat(compositor, compositorSocketPath);\n const stopNotifications = await startNotificationService(`unix:path=${busSocketPath}`);\n const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, \"compositor.stderr.log\"));\n const stopExitWatch = watchCompositorExit(compositor.child, capturedStderr);\n\n return makeTeardown([\n stopExitWatch,\n () => {\n killSpawned(spawned);\n },\n stopVirtualSeat,\n stopNotifications,\n removeRuntime,\n ]);\n } catch (error) {\n killSpawned(spawned);\n removeRuntime();\n throw error;\n }\n};\n\nexport {\n STATIC_HEADLESS_ENV,\n resolveHeadlessOptions,\n readHeadlessOptions,\n startHeadlessDisplay,\n type CompositorId,\n type HeadlessOptions,\n};\n"]}
1
+ {"version":3,"file":"headless-display.js","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EACd,iBAAiB,EACjB,0BAA0B,EAC1B,8BAA8B,GACjC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAqB,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACH,eAAe,EACf,2BAA2B,EAC3B,gBAAgB,EAChB,uBAAuB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAgErD,MAAM,qBAAqB,GAAG,UAAU,CAAC;AACzC,MAAM,2BAA2B,GAAiB,MAAM,CAAC;AACzD,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAEpD,MAAM,iBAAiB,GAAG,yBAAyB,EAAE,CAAC;AAEtD,MAAM,kBAAkB,GAA+C;IACnE,IAAI,EAAE;QACF,MAAM,EAAE,WAAW;QACnB,mBAAmB,EAAE,IAAI;QACzB,GAAG,EAAE;YACD,YAAY,EAAE,UAAU;YACxB,YAAY,EAAE,QAAQ;YACtB,2BAA2B,EAAE,GAAG;YAChC,uBAAuB,EAAE,GAAG;YAC5B,oBAAoB,EAAE,GAAG;SAC5B;QACD,KAAK,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YACjC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;YAEjD,aAAa,CACT,UAAU,EACV,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAClC,CAAC;YAEF,OAAO,8BAA8B,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE;gBAC9D,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;gBACnC,gBAAgB,EAAE,UAAU;aAC/B,CAAC,CAAC;QACP,CAAC;KACJ;IACD,MAAM,EAAE;QACJ,MAAM,EAAE,WAAW;QACnB,mBAAmB,EAAE,KAAK;QAC1B,GAAG,EAAE,EAAE;QACP,KAAK,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAClC,0BAA0B,CACtB,QAAQ,EACR;YACI,oBAAoB;YACpB,mBAAmB;YACnB,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,WAAW,KAAK,EAAE;YAClB,YAAY,MAAM,EAAE;YACpB,oBAAoB;SACvB,EACD,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAC,WAAW,CAAC,EAAE,CAC7E;KACR;CACJ,CAAC;AAEF,MAAM,mBAAmB,GAAG;IACxB,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,QAAQ;IACrB,SAAS,EAAE,UAAU;IACrB,YAAY,EAAE,OAAO;IACrB,QAAQ,EAAE,MAAM;IAChB,qBAAqB,EAAE,GAAG;IAC1B,aAAa,EAAE,MAAM;IACrB,iBAAiB,EAAE,QAAQ;IAC3B,cAAc,EAAE,MAAM;IACtB,eAAe,EAAE,GAAG;CACvB,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,QAAkC,EAAmB,EAAE;IACnF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,qBAAqB,CAAC;IAEpD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO;QACH,IAAI;QACJ,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,2BAA2B;KACjE,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,QAAqB,EAAE,MAA8B,EAAQ,EAAE;IAC7E,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC9B,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,QAAqB,EAAQ,EAAE;IAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC9C,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;QACjC,CAAC;IACL,CAAC;AACL,CAAC,CAAC;AAEF,SAAS,yBAAyB;IAC9B,IAAI,WAAgC,CAAC;IAErC,OAAO,GAAG,EAAE;QACR,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,SAAS,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;YACtF,WAAW,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,WAAW,CAAC;IACvB,CAAC,CAAC;AACN,CAAC;AAED,MAAM,cAAc,GAAG,CAAC,KAAa,EAAyB,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;AAE1G,MAAM,mBAAmB,GAAG,CAAC,MAAuB,EAA4B,EAAE;IAC9E,MAAM,OAAO,GAA6B,EAAE,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEhC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAE5C,IAAI,UAAU,KAAK,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;IACpC,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,UAAkB,EAAE,OAAwB,EAAE,GAAgB,EAAqB,EAAE;IAC1G,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,GAAG,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;IAE9B,OAAO;QACH,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC;QAClD,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,mBAAmB,EAAE,UAAU,CAAC,mBAAmB;KACtD,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,GAAS,EAAE,CAAC,SAAS,CAAC;AAE5C,MAAM,cAAc,GAAG,CAAC,aAAqB,EAAE,aAAqB,EAAQ,EAAE;IAC1E,aAAa,CAAC,aAAa,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC;AACjE,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,KAAa,EAAE,IAAY,EAAE,IAAmB,EAAE,MAA6B,EAAU,EAAE,CAC9G,GAAG,KAAK,iBAAiB,MAAM,CAAC,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,YAAY,IAAI,WAAW,CAAC;AAEjG,MAAM,YAAY,GAAG,CAAC,KAAmB,EAAE,KAAa,EAAE,IAAY,EAAgB,EAAE;IACpF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,MAAM,WAAW,GAAmC,IAAI,GAAG,EAAE,CAAC;IAC9D,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,OAA2B,CAAC;IAEhC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAQ,EAAE;QACnC,GAAG,IAAI,KAAK,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE3B,MAAM,MAAM,GAAG,CAAC,KAAa,EAAQ,EAAE;QACnC,OAAO,KAAK,KAAK,CAAC;QAElB,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;YAC/B,MAAM,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;IACL,CAAC,CAAC;IAEF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,MAA6B,EAAE,EAAE;QACrE,MAAM,CAAC,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;QAC/B,MAAM,CAAC,GAAG,KAAK,2BAA2B,IAAI,cAAc,KAAK,CAAC,OAAO,KAAK,GAAG,EAAE,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,OAAO;QACH,KAAK;QACL,IAAI;QACJ,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG;QACf,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO;QACtB,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,IAAI,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC;QAClG,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE;YAClB,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAExB,OAAO,GAAS,EAAE;gBACd,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC/B,CAAC,CAAC;QACN,CAAC;QACD,IAAI,EAAE,GAAG,EAAE;YACP,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACvC,MAAM,EAAE,MAAM,EAAE,CAAC;YAEjB,IAAI,MAAM,YAAY,MAAM,EAAE,CAAC;gBAC3B,MAAM,CAAC,KAAK,EAAE,CAAC;YACnB,CAAC;QACL,CAAC;KACJ,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,QAAwB,EAAQ,EAAE;IACnD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACd,CAAC;IAED,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,OAAqB,EAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3G,MAAM,eAAe,GAAG,CAAC,QAAwB,EAAkB,EAAE,CACjE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAE3D,MAAM,mBAAmB,GAAG,CAAC,QAAwB,EAAW,EAAE,CAC9D,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAEzD,MAAM,YAAY,GAAG,CAAC,QAAwB,EAAsB,EAAE;IAClE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAElC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC;QACnB,CAAC;IACL,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC,MAAM,cAAc,GAAG,CAAC,OAAuB,EAAE,OAAe,EAAU,EAAE,CACxE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,oCAAoC,MAAM,CAAC,OAAO,CAAC,MAAM;IAC9G,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAEtD,MAAM,eAAe,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAgB,EAAQ,EAAE;IACzE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,iBAAiB,EAAE,GAAG,OAAO,CAAC;IAC1D,MAAM,QAAQ,GAAmB,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzE,MAAM,IAAI,GAAG,GAAS,EAAE;QACpB,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,OAAe,EAAQ,EAAE;QACnC,IAAI,EAAE,CAAC;QACP,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/B,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,aAAa,CAAC,CAAC;QAEpB,OAAO;IACX,CAAC;IAED,MAAM,MAAM,GAAG,GAAS,EAAE;QACtB,IAAI,EAAE,CAAC;QACP,OAAO,EAAE,CAAC;IACd,CAAC,CAAC;IAEF,IAAI,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,MAAM,EAAE,CAAC;QAET,OAAO;IACX,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC1B,IAAI,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChC,MAAM,EAAE,CAAC;QACb,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC1B,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC,EAAE,OAAO,CAAC,CAAC;IAEZ,QAAQ,CAAC,IAAI,CACT,GAAG,EAAE;QACD,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,YAAY,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC,EACD,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CACxD,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,OAA8B,EAAiB,EAAE,CACrE,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC5B,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEP,MAAM,uBAAuB,GAAG,CAAC,KAAmB,EAAE,OAAe,EAAkB,EAAE;IACrF,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAE5B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAC9D,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC1C,IAAI,MAAM,GAAG,IAAI,CAAC;IAElB,MAAM,MAAM,GAAG,CAAC,KAAa,EAAQ,EAAE;QACnC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAErB,IAAI,CAAC;YACD,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACL,OAAO;QACX,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE1B,OAAO;QACH,MAAM,EAAE,QAAQ;QAChB,OAAO;QACP,IAAI,EAAE,GAAG,EAAE;YACP,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAEtC,IAAI,MAAM,EAAE,CAAC;gBACT,MAAM,GAAG,KAAK,CAAC;gBACf,SAAS,CAAC,UAAU,CAAC,CAAC;YAC1B,CAAC;QACL,CAAC;KACJ,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAC1B,IAAmB,EACnB,MAA6B,EAC7B,QAAwB,EAClB,EAAE,CACR,+CAA+C,MAAM,CAAC,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,KAAK;IAC5F,wDAAwD;IACxD,6BAA6B,QAAQ,CAAC,OAAO,8BAA8B,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AAE1G,MAAM,mBAAmB,GAAG,CAAC,KAAmB,EAAE,QAAwB,EAAgB,EAAE;IACxF,MAAM,MAAM,GAAG,CAAC,IAAmB,EAAE,MAA6B,EAAQ,EAAE;QACxE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEzB,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,EAAE,iBAAiB,EAAE,UAAU,EAAkB,EAAiB,EAAE,CAC/F,cAAc,CAAC,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,iBAAiB,CAAC,EAAE,CAAC,CAAC;AAElE,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,MAAM,KAAK,GAAG,CAAC,QAAgB,EAAiB,EAAE,CAC9C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACpB,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEP,MAAM,cAAc,GAAG,CAAC,IAAY,EAA8B,EAAE,CAChE,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACpB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;QAClC,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEP,MAAM,kBAAkB,GAAG,CAAC,OAAqB,EAAE,KAAY,EAAU,EAAE,CACvE,GAAG,OAAO,CAAC,KAAK,mCAAmC,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;AAE3G,MAAM,oBAAoB,GAAG,KAAK,EAAE,OAAqB,EAAiB,EAAE;IACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;IAEhD,SAAS,CAAC;QACN,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAEnD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAClC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,KAAK,EAChC,UAA6B,EAC7B,OAAqB,EACF,EAAE;IACrB,IAAI,UAAU,CAAC,mBAAmB,EAAE,CAAC;QACjC,OAAO,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAEpC,OAAO,aAAa,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,QAAwB,EAAQ,EAAE;IACnD,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAqB,EAAgB,EAAE;IACzD,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,OAAO,GAAS,EAAE;QACd,IAAI,UAAU,EAAE,CAAC;YACb,OAAO;QACX,CAAC;QAED,UAAU,GAAG,IAAI,CAAC;QAClB,WAAW,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,8BAA8B,GAAG,GAAW,EAAE;IAChD,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;IAE5D,IAAI,CAAC;QACD,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC;QAC7D,aAAa,CAAC,UAAU,EAAE,2BAA2B,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAChG,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAE7B,OAAO,UAAU,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,KAAK,EAAE,OAAwB,EAAuB,EAAE;IACjF,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAG,8BAA8B,EAAE,CAAC;IACpD,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,MAAM,aAAa,GAAG,GAAS,EAAE;QAC7B,UAAU,CAAC,GAAG,CAAC,CAAC;QAChB,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC;IAEF,IAAI,CAAC;QACD,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;QACnC,QAAQ,CAAC,GAAG,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACvD,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC9C,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAE7C,MAAM,QAAQ,GAAG,0BAA0B,CAAC,aAAa,EAAE,CAAC,iBAAiB,aAAa,EAAE,CAAC,EAAE;YAC3F,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;YACnC,kBAAkB,EAAE,CAAC,UAAU,CAAC;SACnC,CAAC,CAAC;QAEH,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,MAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,EAAE,mBAAmB,EAAE,aAAa,CAAC,CAAC;QAC9E,QAAQ,CAAC,GAAG,EAAE,EAAE,wBAAwB,EAAE,aAAa,aAAa,EAAE,EAAE,CAAC,CAAC;QAC1E,MAAM,UAAU,GAAG,eAAe,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC7D,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QACjE,MAAM,iBAAiB,GAAG,YAAY,CAAC,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,oBAAoB,CAAC,CAAC;QAC7F,QAAQ,CAAC,GAAG,EAAE,EAAE,eAAe,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACtD,MAAM,qBAAqB,CAAC,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC,CAAC;QAC/D,MAAM,eAAe,GAAG,MAAM,sBAAsB,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;QACpF,MAAM,iBAAiB,GAAG,MAAM,wBAAwB,CAAC,aAAa,aAAa,EAAE,CAAC,CAAC;QACvF,MAAM,cAAc,GAAG,uBAAuB,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC,CAAC;QAC5G,MAAM,aAAa,GAAG,mBAAmB,CAAC,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QAE5E,OAAO,YAAY,CAAC;YAChB,aAAa;YACb,cAAc,CAAC,IAAI;YACnB,GAAG,EAAE;gBACD,WAAW,CAAC,OAAO,CAAC,CAAC;YACzB,CAAC;YACD,eAAe;YACf,iBAAiB;YACjB,aAAa;SAChB,CAAC,CAAC;IACP,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,WAAW,CAAC,OAAO,CAAC,CAAC;QACrB,aAAa,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC,CAAC;AAEF,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,GAGvB,CAAC","sourcesContent":["import {\n isProcessAlive,\n resolveExecutable,\n spawnWithParentDeathSignal,\n spawnWithParentDeathSupervisor,\n} from \"@gtkx/utils\";\nimport { type ChildProcess, spawnSync } from \"node:child_process\";\nimport { chmodSync, closeSync, existsSync, mkdtempSync, openSync, rmSync, writeFileSync, writeSync } from \"node:fs\";\nimport { connect, Socket } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n createBusConfig,\n createHeadlessRuntimeMarker,\n createSwayConfig,\n HEADLESS_RUNTIME_MARKER,\n} from \"./headless-config.ts\";\nimport { startNotificationService } from \"./notification-service.ts\";\nimport { startVirtualSeat } from \"./virtual-seat.ts\";\n\n/**\n * Wayland compositors that can back a headless display.\n */\ntype CompositorId = \"sway\" | \"weston\";\n\n/**\n * Settings for the per-worker headless Wayland display.\n */\ntype HeadlessOptions = {\n /** Output resolution as a \"WIDTHxHEIGHT\" string, for example \"1024x768\". */\n size: string;\n /** Wayland compositor used to back the headless display. */\n compositor: CompositorId;\n};\n\ntype EnvSnapshot = Record<string, string | undefined>;\n\ntype CompositorDescriptor = {\n socket: string;\n env: Record<string, string>;\n requiresVirtualSeat: boolean;\n start: (runtimeDir: string, width: string, height: string) => ChildProcess;\n};\n\ntype SpawnedCompositor = {\n child: ChildProcess;\n socket: string;\n requiresVirtualSeat: boolean;\n};\n\ntype ChildMonitor = {\n label: string;\n path: string;\n read: () => string;\n failure: () => string | undefined;\n isRunning: () => boolean;\n subscribe: (notify: (failure: string) => void) => () => void;\n stop: () => void;\n};\n\ntype WaitForSocketsOptions = {\n monitors: ChildMonitor[];\n timeout?: number;\n};\n\ntype DisplaySockets = {\n compositorMonitor: ChildMonitor;\n busMonitor: ChildMonitor;\n};\n\ntype CapturedStderr = {\n chunks: string[];\n logPath: string;\n stop: () => void;\n};\n\ntype SocketsWatch = {\n options: WaitForSocketsOptions;\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nconst DEFAULT_HEADLESS_SIZE = \"1024x768\";\nconst DEFAULT_HEADLESS_COMPOSITOR: CompositorId = \"sway\";\nconst HEADLESS_SIZE_PATTERN = /^[1-9]\\d*x[1-9]\\d*$/;\n\nconst hasWestonFakeSeat = createWestonFakeSeatProbe();\n\nconst compositorRegistry: Record<CompositorId, CompositorDescriptor> = {\n sway: {\n socket: \"wayland-1\",\n requiresVirtualSeat: true,\n env: {\n WLR_BACKENDS: \"headless\",\n WLR_RENDERER: \"pixman\",\n WLR_RENDERER_ALLOW_SOFTWARE: \"1\",\n WLR_LIBINPUT_NO_DEVICES: \"1\",\n WLR_HEADLESS_OUTPUTS: \"1\",\n },\n start: (runtimeDir, width, height) => {\n const configPath = join(runtimeDir, \"sway.conf\");\n\n writeFileSync(\n configPath,\n createSwayConfig(width, height),\n );\n\n return spawnWithParentDeathSupervisor(\"sway\", [\"-c\", configPath], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n cleanupDirectory: runtimeDir,\n });\n },\n },\n weston: {\n socket: \"wayland-0\",\n requiresVirtualSeat: false,\n env: {},\n start: (_runtimeDir, width, height) =>\n spawnWithParentDeathSignal(\n \"weston\",\n [\n \"--backend=headless\",\n \"--renderer=pixman\",\n ...(hasWestonFakeSeat() ? [\"--fake-seat\"] : []),\n `--width=${width}`,\n `--height=${height}`,\n \"--socket=wayland-0\",\n ],\n { stdio: [\"ignore\", \"ignore\", \"pipe\"], cleanupDirectories: [_runtimeDir] },\n ),\n },\n};\n\nconst STATIC_HEADLESS_ENV = {\n GDK_BACKEND: \"wayland\",\n GDK_DISABLE: \"vulkan\",\n GDK_DEBUG: \"no-vsync\",\n GSK_RENDERER: \"cairo\",\n GTK_A11Y: \"test\",\n LIBGL_ALWAYS_SOFTWARE: \"1\",\n GST_GL_WINDOW: \"none\",\n GSETTINGS_BACKEND: \"memory\",\n ALSOFT_DRIVERS: \"null\",\n ALSOFT_LOGLEVEL: \"0\",\n};\n\nconst resolveHeadlessOptions = (provided: Partial<HeadlessOptions>): HeadlessOptions => {\n const size = provided.size ?? DEFAULT_HEADLESS_SIZE;\n\n if (!HEADLESS_SIZE_PATTERN.test(size)) {\n throw new Error(`Invalid headless display size: ${size}`);\n }\n\n return {\n size,\n compositor: provided.compositor ?? DEFAULT_HEADLESS_COMPOSITOR,\n };\n};\n\nconst applyEnv = (snapshot: EnvSnapshot, values: Record<string, string>): void => {\n for (const [name, value] of Object.entries(values)) {\n if (!Object.hasOwn(snapshot, name)) {\n snapshot[name] = process.env[name];\n }\n\n process.env[name] = value;\n }\n};\n\nconst restoreEnv = (snapshot: EnvSnapshot): void => {\n for (const [name, previous] of Object.entries(snapshot)) {\n if (previous === undefined) {\n Reflect.deleteProperty(process.env, name);\n } else {\n process.env[name] = previous;\n }\n }\n};\n\nfunction createWestonFakeSeatProbe(): () => boolean {\n let isSupported: boolean | undefined;\n\n return () => {\n if (isSupported === undefined) {\n const help = spawnSync(resolveExecutable(\"weston\"), [\"--help\"], { encoding: \"utf8\" });\n isSupported = `${help.stdout}${help.stderr}`.includes(\"--fake-seat\");\n }\n\n return isSupported;\n };\n}\n\nconst isCompositorId = (value: string): value is CompositorId => Object.hasOwn(compositorRegistry, value);\n\nconst readHeadlessOptions = (params: URLSearchParams): Partial<HeadlessOptions> => {\n const options: Partial<HeadlessOptions> = {};\n const size = params.get(\"size\");\n\n if (size !== null) {\n options.size = size;\n }\n\n const compositor = params.get(\"compositor\");\n\n if (compositor !== null && isCompositorId(compositor)) {\n options.compositor = compositor;\n }\n\n return options;\n};\n\nconst startCompositor = (runtimeDir: string, options: HeadlessOptions, env: EnvSnapshot): SpawnedCompositor => {\n const descriptor = compositorRegistry[options.compositor];\n const [width = \"\", height = \"\"] = options.size.split(\"x\", 2);\n applyEnv(env, descriptor.env);\n\n return {\n child: descriptor.start(runtimeDir, width, height),\n socket: descriptor.socket,\n requiresVirtualSeat: descriptor.requiresVirtualSeat,\n };\n};\n\nconst noVirtualSeat = (): void => undefined;\n\nconst writeBusConfig = (busConfigPath: string, busSocketPath: string): void => {\n writeFileSync(busConfigPath, createBusConfig(busSocketPath));\n};\n\nconst exitedMessage = (label: string, path: string, code: number | null, signal: NodeJS.Signals | null): string =>\n `${label} exited (code ${String(code)}, signal ${signal ?? \"null\"}) before ${path} appeared`;\n\nconst monitorChild = (child: ChildProcess, label: string, path: string): ChildMonitor => {\n const stderr = child.stderr;\n const subscribers: Set<(failure: string) => void> = new Set();\n let log = \"\";\n let failure: string | undefined;\n\n const onData = (chunk: string): void => {\n log += chunk;\n };\n\n stderr?.setEncoding(\"utf8\");\n stderr?.on(\"data\", onData);\n\n const record = (cause: string): void => {\n failure ??= cause;\n\n for (const notify of subscribers) {\n notify(cause);\n }\n };\n\n child.on(\"close\", (code: number | null, signal: NodeJS.Signals | null) => {\n record(`${exitedMessage(label, path, code, signal)}\\n${log}`);\n });\n\n child.on(\"error\", (cause: Error) => {\n record(`${label} failed to spawn before ${path} appeared: ${cause.message}\\n${log}`);\n });\n\n return {\n label,\n path,\n read: () => log,\n failure: () => failure,\n isRunning: () => child.exitCode === null && child.signalCode === null && isProcessAlive(child.pid),\n subscribe: (notify) => {\n subscribers.add(notify);\n\n return (): void => {\n subscribers.delete(notify);\n };\n },\n stop: () => {\n stderr?.removeListener(\"data\", onData);\n stderr?.resume();\n\n if (stderr instanceof Socket) {\n stderr.unref();\n }\n },\n };\n};\n\nconst runCleanups = (cleanups: (() => void)[]): void => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n\n cleanups.length = 0;\n};\n\nconst isMonitorReady = (monitor: ChildMonitor): boolean => monitor.isRunning() && existsSync(monitor.path);\n\nconst pendingMonitors = (monitors: ChildMonitor[]): ChildMonitor[] =>\n monitors.filter((monitor) => !isMonitorReady(monitor));\n\nconst isEveryMonitorReady = (monitors: ChildMonitor[]): boolean =>\n monitors.every((monitor) => isMonitorReady(monitor));\n\nconst firstFailure = (monitors: ChildMonitor[]): string | undefined => {\n for (const monitor of monitors) {\n const failure = monitor.failure();\n\n if (failure !== undefined) {\n return failure;\n }\n }\n\n return undefined;\n};\n\nconst SOCKET_TIMEOUT_MS = 15_000;\n\nconst missingMessage = (pending: ChildMonitor[], timeout: number): string =>\n `${pending.map((monitor) => monitor.label).join(\", \")} did not become available within ${String(timeout)}ms\\n` +\n pending.map((monitor) => monitor.read()).join(\"\");\n\nconst watchForSockets = ({ options, resolve, reject }: SocketsWatch): void => {\n const { monitors, timeout = SOCKET_TIMEOUT_MS } = options;\n const cleanups: (() => void)[] = monitors.map((monitor) => monitor.stop);\n\n const stop = (): void => {\n runCleanups(cleanups);\n };\n\n const fail = (message: string): void => {\n stop();\n reject(new Error(message));\n };\n\n const alreadyFailed = firstFailure(monitors);\n\n if (alreadyFailed !== undefined) {\n fail(alreadyFailed);\n\n return;\n }\n\n const settle = (): void => {\n stop();\n resolve();\n };\n\n if (isEveryMonitorReady(monitors)) {\n settle();\n\n return;\n }\n\n const poll = setInterval(() => {\n if (isEveryMonitorReady(monitors)) {\n settle();\n }\n }, 10);\n\n const timer = setTimeout(() => {\n fail(missingMessage(pendingMonitors(monitors), timeout));\n }, timeout);\n\n cleanups.push(\n () => {\n clearInterval(poll);\n clearTimeout(timer);\n },\n ...monitors.map((monitor) => monitor.subscribe(fail)),\n );\n};\n\nconst waitForSockets = (options: WaitForSocketsOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n watchForSockets({ options, resolve, reject });\n });\n\nconst captureCompositorStderr = (child: ChildProcess, logPath: string): CapturedStderr => {\n const captured: string[] = [];\n const stderr = child.stderr;\n\n if (stderr === null) {\n return { chunks: captured, logPath, stop: noVirtualSeat };\n }\n\n stderr.setEncoding(\"utf8\");\n const descriptor = openSync(logPath, \"a\");\n let isOpen = true;\n\n const onData = (chunk: string): void => {\n captured.push(chunk);\n\n try {\n writeSync(descriptor, chunk);\n } catch {\n return;\n }\n };\n\n stderr.on(\"data\", onData);\n\n return {\n chunks: captured,\n logPath,\n stop: () => {\n stderr.removeListener(\"data\", onData);\n\n if (isOpen) {\n isOpen = false;\n closeSync(descriptor);\n }\n },\n };\n};\n\nconst compositorExitMessage = (\n code: number | null,\n signal: NodeJS.Signals | null,\n captured: CapturedStderr,\n): string =>\n `[gtkx] the headless compositor exited (code ${String(code)}, signal ${signal ?? \"null\"}); ` +\n \"every Wayland client in this worker has been severed. \" +\n `Its stderr log is kept at ${captured.logPath} until this worker exits.\\n${captured.chunks.join(\"\")}`;\n\nconst watchCompositorExit = (child: ChildProcess, captured: CapturedStderr): (() => void) => {\n const report = (code: number | null, signal: NodeJS.Signals | null): void => {\n process.stderr.write(compositorExitMessage(code, signal, captured));\n };\n\n child.on(\"exit\", report);\n\n return () => child.removeListener(\"exit\", report);\n};\n\nconst waitForDisplaySockets = ({ compositorMonitor, busMonitor }: DisplaySockets): Promise<void> =>\n waitForSockets({ monitors: [busMonitor, compositorMonitor] });\n\nconst CONNECT_RETRY_MS = 10;\n\nconst pause = (duration: number): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, duration);\n });\n\nconst connectFailure = (path: string): Promise<Error | undefined> =>\n new Promise((resolve) => {\n const socket = connect(path);\n\n socket.once(\"connect\", () => {\n socket.destroy();\n resolve(undefined);\n });\n\n socket.once(\"error\", (cause: Error) => {\n socket.destroy();\n resolve(cause);\n });\n });\n\nconst unreachableMessage = (monitor: ChildMonitor, cause: Error): string =>\n `${monitor.label} did not accept a connection on ${monitor.path}: ${cause.message}\\n${monitor.read()}`;\n\nconst waitUntilConnectable = async (monitor: ChildMonitor): Promise<void> => {\n const deadline = Date.now() + SOCKET_TIMEOUT_MS;\n\n for (;;) {\n const failure = await connectFailure(monitor.path);\n\n if (failure === undefined) {\n return;\n }\n\n if (!monitor.isRunning() || Date.now() >= deadline) {\n throw new Error(unreachableMessage(monitor, failure));\n }\n\n await pause(CONNECT_RETRY_MS);\n }\n};\n\nconst attachCompositorClient = async (\n compositor: SpawnedCompositor,\n monitor: ChildMonitor,\n): Promise<() => void> => {\n if (compositor.requiresVirtualSeat) {\n return startVirtualSeat(monitor.path);\n }\n\n await waitUntilConnectable(monitor);\n\n return noVirtualSeat;\n};\n\nconst killSpawned = (children: ChildProcess[]): void => {\n for (const child of children) {\n child.kill(\"SIGTERM\");\n }\n};\n\nconst makeTeardown = (stops: (() => void)[]): (() => void) => {\n let isTorndown = false;\n\n return (): void => {\n if (isTorndown) {\n return;\n }\n\n isTorndown = true;\n runCleanups(stops);\n };\n};\n\nconst createHeadlessRuntimeDirectory = (): string => {\n const runtimeDir = mkdtempSync(join(tmpdir(), \"gtkx-xdg-\"));\n\n try {\n chmodSync(runtimeDir, 0o700);\n const markerPath = join(runtimeDir, HEADLESS_RUNTIME_MARKER);\n writeFileSync(markerPath, createHeadlessRuntimeMarker(runtimeDir), { flag: \"wx\", mode: 0o600 });\n chmodSync(markerPath, 0o600);\n\n return runtimeDir;\n } catch (error) {\n rmSync(runtimeDir, { recursive: true, force: true });\n throw error;\n }\n};\n\nconst startHeadlessDisplay = async (options: HeadlessOptions): Promise<() => void> => {\n const env: EnvSnapshot = {};\n const runtimeDir = createHeadlessRuntimeDirectory();\n const spawned: ChildProcess[] = [];\n\n const removeRuntime = (): void => {\n restoreEnv(env);\n rmSync(runtimeDir, { recursive: true, force: true });\n };\n\n try {\n applyEnv(env, STATIC_HEADLESS_ENV);\n applyEnv(env, { XDG_RUNTIME_DIR: runtimeDir });\n const busConfigPath = join(runtimeDir, \"session.conf\");\n const busSocketPath = join(runtimeDir, \"bus\");\n writeBusConfig(busConfigPath, busSocketPath);\n\n const busChild = spawnWithParentDeathSignal(\"dbus-daemon\", [`--config-file=${busConfigPath}`], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n cleanupDirectories: [runtimeDir],\n });\n\n busChild.unref();\n spawned.push(busChild);\n const busMonitor = monitorChild(busChild, \"D-Bus session bus\", busSocketPath);\n applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });\n const compositor = startCompositor(runtimeDir, options, env);\n compositor.child.unref();\n spawned.push(compositor.child);\n const compositorSocketPath = join(runtimeDir, compositor.socket);\n const compositorMonitor = monitorChild(compositor.child, \"Compositor\", compositorSocketPath);\n applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n await waitForDisplaySockets({ compositorMonitor, busMonitor });\n const stopVirtualSeat = await attachCompositorClient(compositor, compositorMonitor);\n const stopNotifications = await startNotificationService(`unix:path=${busSocketPath}`);\n const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, \"compositor.stderr.log\"));\n const stopExitWatch = watchCompositorExit(compositor.child, capturedStderr);\n\n return makeTeardown([\n stopExitWatch,\n capturedStderr.stop,\n () => {\n killSpawned(spawned);\n },\n stopVirtualSeat,\n stopNotifications,\n removeRuntime,\n ]);\n } catch (error) {\n killSpawned(spawned);\n removeRuntime();\n throw error;\n }\n};\n\nexport {\n STATIC_HEADLESS_ENV,\n resolveHeadlessOptions,\n readHeadlessOptions,\n startHeadlessDisplay,\n type CompositorId,\n type HeadlessOptions,\n};\n"]}
@@ -0,0 +1,3 @@
1
+ export { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay, STATIC_HEADLESS_ENV, type CompositorId, type HeadlessOptions, } from "./headless-display.js";
2
+ export { findStaleHeadlessDisplays, reapStaleHeadlessDisplays, reapStaleHeadlessDisplaysAtStartup, type StaleHeadlessDisplay, } from "./reap-headless-displays.js";
3
+ //# sourceMappingURL=headless.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless.d.ts","sourceRoot":"","sources":["../src/headless.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACH,yBAAyB,EACzB,yBAAyB,EACzB,kCAAkC,EAClC,KAAK,oBAAoB,GAC5B,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay, STATIC_HEADLESS_ENV, } from "./headless-display.js";
2
+ export { findStaleHeadlessDisplays, reapStaleHeadlessDisplays, reapStaleHeadlessDisplaysAtStartup, } from "./reap-headless-displays.js";
3
+ //# sourceMappingURL=headless.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless.js","sourceRoot":"","sources":["../src/headless.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,GAGtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACH,yBAAyB,EACzB,yBAAyB,EACzB,kCAAkC,GAErC,MAAM,6BAA6B,CAAC","sourcesContent":["export {\n readHeadlessOptions,\n resolveHeadlessOptions,\n startHeadlessDisplay,\n STATIC_HEADLESS_ENV,\n type CompositorId,\n type HeadlessOptions,\n} from \"./headless-display.js\";\nexport {\n findStaleHeadlessDisplays,\n reapStaleHeadlessDisplays,\n reapStaleHeadlessDisplaysAtStartup,\n type StaleHeadlessDisplay,\n} from \"./reap-headless-displays.js\";\n"]}
package/dist/index.d.ts CHANGED
@@ -4,12 +4,18 @@ import { type HeadlessOptions } from "./headless-display.ts";
4
4
  * Options accepted by the GTKX Vitest plugin. Every headless display
5
5
  * setting is optional and falls back to a built-in default when omitted.
6
6
  */
7
- type PluginOptions = Partial<HeadlessOptions>;
7
+ type PluginOptions = Partial<HeadlessOptions> & Partial<Record<"configFile", string | undefined>>;
8
8
  /**
9
9
  * Vitest plugin that runs each test worker against its own isolated headless
10
10
  * Wayland display. It configures the forks pool, injects the worker preload and
11
11
  * setup files, and sets the environment needed for headless GTK4 rendering.
12
12
  *
13
+ * Each worker's compositor, session bus, and private runtime directory are torn
14
+ * down when the worker exits, and a guard process tears them down as well when
15
+ * the worker or the Vitest process that launched it is killed with `SIGKILL`.
16
+ * Creating the plugin reaps stale `gtkx-xdg-*` runtime directories left behind
17
+ * by earlier runs, which `gtkx cleanup` also does on demand.
18
+ *
13
19
  * @param options Headless display settings (size, compositor) forwarded to each worker.
14
20
  * @returns A Vitest config plugin.
15
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAK5C,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;AAElF;;;GAGG;AACH,KAAK,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;AAsB9C;;;;;;;GAOG;AACH,QAAA,MAAM,IAAI,GAAI,UAAS,aAAkB,KAAG,MAoBtC,CAAC;AAEP,eAAe,IAAI,CAAC;AACpB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,KAAK,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAM5C,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;AAGlF;;;GAGG;AACH,KAAK,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAsBlG;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,IAAI,GAAI,UAAS,aAAkB,KAAG,MA2B3C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,KAAK,aAAa,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
+ import { assertSupportedNodeVersion, createConfigLoader } from "@gtkx/config/internal";
1
2
  import createConfigPlugin from "@gtkx/config/vite-plugin";
2
3
  import { existsSync } from "node:fs";
3
4
  import { join } from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { STATIC_HEADLESS_ENV } from "./headless-display.js";
7
+ import { reapStaleHeadlessDisplaysAtStartup } from "./reap-headless-displays.js";
6
8
  const GTKX_INLINE_DEPS = [/@gtkx\/(?!native)/, /[/\\]\.gtkx[/\\]/];
7
9
  const DEFAULT_TIMEOUT = 30_000;
8
10
  const workerPreloadUrl = () => {
@@ -22,29 +24,42 @@ const headlessPreloadSpecifier = (options) => {
22
24
  * Wayland display. It configures the forks pool, injects the worker preload and
23
25
  * setup files, and sets the environment needed for headless GTK4 rendering.
24
26
  *
27
+ * Each worker's compositor, session bus, and private runtime directory are torn
28
+ * down when the worker exits, and a guard process tears them down as well when
29
+ * the worker or the Vitest process that launched it is killed with `SIGKILL`.
30
+ * Creating the plugin reaps stale `gtkx-xdg-*` runtime directories left behind
31
+ * by earlier runs, which `gtkx cleanup` also does on demand.
32
+ *
25
33
  * @param options Headless display settings (size, compositor) forwarded to each worker.
26
34
  * @returns A Vitest config plugin.
27
35
  */
28
- const gtkx = (options = {}) => createConfigPlugin({
29
- name: "gtkx:vitest",
30
- config(config) {
31
- return {
32
- test: {
33
- globals: true,
34
- execArgv: ["--import", headlessPreloadSpecifier(options)],
35
- testTimeout: config.test?.testTimeout ?? DEFAULT_TIMEOUT,
36
- hookTimeout: config.test?.hookTimeout ?? DEFAULT_TIMEOUT,
37
- pool: "forks",
38
- env: STATIC_HEADLESS_ENV,
39
- server: {
40
- deps: {
41
- inline: GTKX_INLINE_DEPS,
36
+ const gtkx = (options = {}) => {
37
+ assertSupportedNodeVersion();
38
+ reapStaleHeadlessDisplaysAtStartup();
39
+ const { configFile, ...headlessOptions } = options;
40
+ const loadConfig = createConfigLoader({ configFile });
41
+ return createConfigPlugin({
42
+ name: "gtkx:vitest",
43
+ loadConfig,
44
+ config(config) {
45
+ return {
46
+ test: {
47
+ globals: true,
48
+ execArgv: ["--disable-sigusr1", "--import", headlessPreloadSpecifier(headlessOptions)],
49
+ testTimeout: config.test?.testTimeout ?? DEFAULT_TIMEOUT,
50
+ hookTimeout: config.test?.hookTimeout ?? DEFAULT_TIMEOUT,
51
+ pool: "forks",
52
+ env: STATIC_HEADLESS_ENV,
53
+ server: {
54
+ deps: {
55
+ inline: GTKX_INLINE_DEPS,
56
+ },
42
57
  },
43
58
  },
44
- },
45
- };
46
- },
47
- });
59
+ };
60
+ },
61
+ });
62
+ };
48
63
  export default gtkx;
49
64
  export {} from "./headless-display.js";
50
65
  export {};
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAwB,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAQlF,MAAM,gBAAgB,GAAa,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;AAC7E,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,gBAAgB,GAAG,GAAQ,EAAE;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAE1G,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAAC,OAAsB,EAAU,EAAE;IAChE,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC;AACpB,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,IAAI,GAAG,CAAC,UAAyB,EAAE,EAAU,EAAE,CACjD,kBAAkB,CAAC;IACf,IAAI,EAAE,aAAa;IACnB,MAAM,CAAC,MAAM;QACT,OAAO;YACH,IAAI,EAAE;gBACF,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,CAAC,UAAU,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;gBACzD,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,eAAe;gBACxD,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,eAAe;gBACxD,IAAI,EAAE,OAAO;gBACb,GAAG,EAAE,mBAAmB;gBACxB,MAAM,EAAE;oBACJ,IAAI,EAAE;wBACF,MAAM,EAAE,gBAAgB;qBAC3B;iBACJ;aACJ;SACJ,CAAC;IACN,CAAC;CACJ,CAAC,CAAC;AAEP,eAAe,IAAI,CAAC;AACpB,OAAO,EAA2C,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAsB,CAAC","sourcesContent":["import type { Plugin } from \"vitest/config\";\nimport createConfigPlugin from \"@gtkx/config/vite-plugin\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { type HeadlessOptions, STATIC_HEADLESS_ENV } from \"./headless-display.ts\";\n\n/**\n * Options accepted by the GTKX Vitest plugin. Every headless display\n * setting is optional and falls back to a built-in default when omitted.\n */\ntype PluginOptions = Partial<HeadlessOptions>;\n\nconst GTKX_INLINE_DEPS: RegExp[] = [/@gtkx\\/(?!native)/, /[/\\\\]\\.gtkx[/\\\\]/];\nconst DEFAULT_TIMEOUT = 30_000;\n\nconst workerPreloadUrl = (): URL => {\n const sibling = join(import.meta.dirname, \"worker-preload.js\");\n const path = existsSync(sibling) ? sibling : join(import.meta.dirname, \"..\", \"dist\", \"worker-preload.js\");\n\n return pathToFileURL(path);\n};\n\nconst headlessPreloadSpecifier = (options: PluginOptions): string => {\n const url = workerPreloadUrl();\n\n for (const [key, value] of Object.entries(options)) {\n url.searchParams.set(key, value);\n }\n\n return url.href;\n};\n\n/**\n * Vitest plugin that runs each test worker against its own isolated headless\n * Wayland display. It configures the forks pool, injects the worker preload and\n * setup files, and sets the environment needed for headless GTK4 rendering.\n *\n * @param options Headless display settings (size, compositor) forwarded to each worker.\n * @returns A Vitest config plugin.\n */\nconst gtkx = (options: PluginOptions = {}): Plugin =>\n createConfigPlugin({\n name: \"gtkx:vitest\",\n config(config) {\n return {\n test: {\n globals: true,\n execArgv: [\"--import\", headlessPreloadSpecifier(options)],\n testTimeout: config.test?.testTimeout ?? DEFAULT_TIMEOUT,\n hookTimeout: config.test?.hookTimeout ?? DEFAULT_TIMEOUT,\n pool: \"forks\",\n env: STATIC_HEADLESS_ENV,\n server: {\n deps: {\n inline: GTKX_INLINE_DEPS,\n },\n },\n },\n };\n },\n });\n\nexport default gtkx;\nexport { type CompositorId, type HeadlessOptions } from \"./headless-display.ts\";\nexport { type PluginOptions };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,kBAAkB,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAwB,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,kCAAkC,EAAE,MAAM,6BAA6B,CAAC;AAQjF,MAAM,gBAAgB,GAAa,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;AAC7E,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,gBAAgB,GAAG,GAAQ,EAAE;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAE1G,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAAC,OAAiC,EAAU,EAAE;IAC3E,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC;AACpB,CAAC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,IAAI,GAAG,CAAC,UAAyB,EAAE,EAAU,EAAE;IACjD,0BAA0B,EAAE,CAAC;IAC7B,kCAAkC,EAAE,CAAC;IACrC,MAAM,EAAE,UAAU,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC;IACnD,MAAM,UAAU,GAAG,kBAAkB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IAEtD,OAAO,kBAAkB,CAAC;QACtB,IAAI,EAAE,aAAa;QACnB,UAAU;QACV,MAAM,CAAC,MAAM;YACT,OAAO;gBACH,IAAI,EAAE;oBACF,OAAO,EAAE,IAAI;oBACb,QAAQ,EAAE,CAAC,mBAAmB,EAAE,UAAU,EAAE,wBAAwB,CAAC,eAAe,CAAC,CAAC;oBACtF,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,eAAe;oBACxD,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,eAAe;oBACxD,IAAI,EAAE,OAAO;oBACb,GAAG,EAAE,mBAAmB;oBACxB,MAAM,EAAE;wBACJ,IAAI,EAAE;4BACF,MAAM,EAAE,gBAAgB;yBAC3B;qBACJ;iBACJ;aACJ,CAAC;QACN,CAAC;KACJ,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,OAAO,EAA2C,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAsB,CAAC","sourcesContent":["import type { Plugin } from \"vitest/config\";\nimport { assertSupportedNodeVersion, createConfigLoader } from \"@gtkx/config/internal\";\nimport createConfigPlugin from \"@gtkx/config/vite-plugin\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { type HeadlessOptions, STATIC_HEADLESS_ENV } from \"./headless-display.ts\";\nimport { reapStaleHeadlessDisplaysAtStartup } from \"./reap-headless-displays.ts\";\n\n/**\n * Options accepted by the GTKX Vitest plugin. Every headless display\n * setting is optional and falls back to a built-in default when omitted.\n */\ntype PluginOptions = Partial<HeadlessOptions> & Partial<Record<\"configFile\", string | undefined>>;\n\nconst GTKX_INLINE_DEPS: RegExp[] = [/@gtkx\\/(?!native)/, /[/\\\\]\\.gtkx[/\\\\]/];\nconst DEFAULT_TIMEOUT = 30_000;\n\nconst workerPreloadUrl = (): URL => {\n const sibling = join(import.meta.dirname, \"worker-preload.js\");\n const path = existsSync(sibling) ? sibling : join(import.meta.dirname, \"..\", \"dist\", \"worker-preload.js\");\n\n return pathToFileURL(path);\n};\n\nconst headlessPreloadSpecifier = (options: Partial<HeadlessOptions>): string => {\n const url = workerPreloadUrl();\n\n for (const [key, value] of Object.entries(options)) {\n url.searchParams.set(key, value);\n }\n\n return url.href;\n};\n\n/**\n * Vitest plugin that runs each test worker against its own isolated headless\n * Wayland display. It configures the forks pool, injects the worker preload and\n * setup files, and sets the environment needed for headless GTK4 rendering.\n *\n * Each worker's compositor, session bus, and private runtime directory are torn\n * down when the worker exits, and a guard process tears them down as well when\n * the worker or the Vitest process that launched it is killed with `SIGKILL`.\n * Creating the plugin reaps stale `gtkx-xdg-*` runtime directories left behind\n * by earlier runs, which `gtkx cleanup` also does on demand.\n *\n * @param options Headless display settings (size, compositor) forwarded to each worker.\n * @returns A Vitest config plugin.\n */\nconst gtkx = (options: PluginOptions = {}): Plugin => {\n assertSupportedNodeVersion();\n reapStaleHeadlessDisplaysAtStartup();\n const { configFile, ...headlessOptions } = options;\n const loadConfig = createConfigLoader({ configFile });\n\n return createConfigPlugin({\n name: \"gtkx:vitest\",\n loadConfig,\n config(config) {\n return {\n test: {\n globals: true,\n execArgv: [\"--disable-sigusr1\", \"--import\", headlessPreloadSpecifier(headlessOptions)],\n testTimeout: config.test?.testTimeout ?? DEFAULT_TIMEOUT,\n hookTimeout: config.test?.hookTimeout ?? DEFAULT_TIMEOUT,\n pool: \"forks\",\n env: STATIC_HEADLESS_ENV,\n server: {\n deps: {\n inline: GTKX_INLINE_DEPS,\n },\n },\n },\n };\n },\n });\n};\n\nexport default gtkx;\nexport { type CompositorId, type HeadlessOptions } from \"./headless-display.ts\";\nexport { type PluginOptions };\n"]}
@@ -0,0 +1,10 @@
1
+ import { type CleanupDirectoryIdentity } from "@gtkx/utils";
2
+ type StaleHeadlessDisplay = {
3
+ runtimeDir: string;
4
+ cleanupDirectory: CleanupDirectoryIdentity;
5
+ };
6
+ declare const findStaleHeadlessDisplays: () => StaleHeadlessDisplay[];
7
+ declare const reapStaleHeadlessDisplays: (candidates?: readonly StaleHeadlessDisplay[]) => string[];
8
+ declare const reapStaleHeadlessDisplaysAtStartup: () => void;
9
+ export { findStaleHeadlessDisplays, reapStaleHeadlessDisplays, reapStaleHeadlessDisplaysAtStartup, type StaleHeadlessDisplay, };
10
+ //# sourceMappingURL=reap-headless-displays.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reap-headless-displays.d.ts","sourceRoot":"","sources":["../src/reap-headless-displays.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,wBAAwB,EAIhC,MAAM,aAAa,CAAC;AAWrB,KAAK,oBAAoB,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,wBAAwB,CAAC;CAC9C,CAAC;AA8MF,QAAA,MAAM,yBAAyB,QAAO,oBAAoB,EAYzD,CAAC;AAkCF,QAAA,MAAM,yBAAyB,GAC3B,aAAY,SAAS,oBAAoB,EAAgC,KAC1E,MAAM,EAiBR,CAAC;AAEF,QAAA,MAAM,kCAAkC,QAAO,IAS9C,CAAC;AAEF,OAAO,EACH,yBAAyB,EACzB,yBAAyB,EACzB,kCAAkC,EAClC,KAAK,oBAAoB,GAC5B,CAAC"}
@@ -0,0 +1,211 @@
1
+ import { cleanupDirectoryIdentity, info, removeCleanupDirectory, } from "@gtkx/utils";
2
+ import { constants, lstatSync, readdirSync, readFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { basename, dirname, join } from "node:path";
5
+ import { createBusConfig, createHeadlessRuntimeMarker, HEADLESS_RUNTIME_MARKER, isSwayConfig, } from "./headless-config.js";
6
+ const RUNTIME_DIRECTORY_PATTERN = /^gtkx-xdg-[A-Za-z0-9]{6}$/;
7
+ const STOPPED_PROCESS_STATES = new Set(["Z", "X", "x"]);
8
+ const CONFIG_SIZE_LIMIT = 2048;
9
+ const MINIMUM_STALE_AGE_MS = 5000;
10
+ const RUNTIME_ROOT = tmpdir();
11
+ const currentUserId = () => {
12
+ const getuid = process.getuid;
13
+ return getuid === undefined ? undefined : getuid();
14
+ };
15
+ const isUserOwned = (path, userId) => {
16
+ try {
17
+ return lstatSync(path).uid === userId;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ };
23
+ const isPrivateRuntimeDirectory = (runtimeDir, userId) => {
24
+ try {
25
+ const stat = lstatSync(runtimeDir);
26
+ return stat.isDirectory() &&
27
+ stat.uid === userId &&
28
+ (stat.mode & 0o777) === 0o700 &&
29
+ Date.now() - stat.mtimeMs >= MINIMUM_STALE_AGE_MS &&
30
+ RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) &&
31
+ dirname(runtimeDir) === RUNTIME_ROOT;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ };
37
+ const readOwnedFile = (path, runtimeDir, userId, requiredMode) => {
38
+ try {
39
+ if (dirname(path) !== runtimeDir) {
40
+ return undefined;
41
+ }
42
+ const contents = readFileSync(path, {
43
+ encoding: "utf8",
44
+ flag: constants.O_RDONLY | constants.O_NOFOLLOW,
45
+ });
46
+ const stat = lstatSync(path);
47
+ return stat.isFile() &&
48
+ stat.uid === userId &&
49
+ stat.size < CONFIG_SIZE_LIMIT &&
50
+ (requiredMode === undefined || (stat.mode & 0o777) === requiredMode)
51
+ ? contents
52
+ : undefined;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ };
58
+ const hasOnlyBusConfig = (runtimeDir) => {
59
+ try {
60
+ const entries = readdirSync(runtimeDir);
61
+ return entries.length === 1 && entries[0] === "session.conf";
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ };
67
+ const hasGeneratedRuntimeFiles = (runtimeDir, userId) => {
68
+ const bus = readOwnedFile(join(runtimeDir, "session.conf"), runtimeDir, userId);
69
+ if (bus !== createBusConfig(join(runtimeDir, "bus"))) {
70
+ return false;
71
+ }
72
+ const sway = readOwnedFile(join(runtimeDir, "sway.conf"), runtimeDir, userId);
73
+ const marker = readOwnedFile(join(runtimeDir, HEADLESS_RUNTIME_MARKER), runtimeDir, userId, 0o600);
74
+ return marker === createHeadlessRuntimeMarker(runtimeDir) ||
75
+ (sway !== undefined && isSwayConfig(sway)) ||
76
+ hasOnlyBusConfig(runtimeDir);
77
+ };
78
+ const readProcessArguments = (pid) => {
79
+ try {
80
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
81
+ const state = stat.slice(stat.lastIndexOf(") ") + 2).split(" ", 1)[0];
82
+ if (state === undefined || STOPPED_PROCESS_STATES.has(state)) {
83
+ return undefined;
84
+ }
85
+ return readFileSync(`/proc/${String(pid)}/cmdline`)
86
+ .toString()
87
+ .split("\0")
88
+ .filter((argument) => argument.length > 0);
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ };
94
+ const runtimeFromArgument = (argument) => {
95
+ const path = argument.startsWith("--config-file=") ? argument.slice("--config-file=".length) : argument;
96
+ const name = basename(path);
97
+ if (name !== "sway.conf" && name !== "session.conf") {
98
+ return undefined;
99
+ }
100
+ const runtimeDir = dirname(path);
101
+ return RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) && dirname(runtimeDir) === RUNTIME_ROOT
102
+ ? runtimeDir
103
+ : undefined;
104
+ };
105
+ const runtimeFromEnvironment = (pid) => {
106
+ try {
107
+ const prefix = "XDG_RUNTIME_DIR=";
108
+ const entry = readFileSync(`/proc/${String(pid)}/environ`, "utf8")
109
+ .split("\0")
110
+ .find((value) => value.startsWith(prefix));
111
+ const runtimeDir = entry?.slice(prefix.length);
112
+ return runtimeDir !== undefined &&
113
+ RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) &&
114
+ dirname(runtimeDir) === RUNTIME_ROOT
115
+ ? runtimeDir
116
+ : undefined;
117
+ }
118
+ catch {
119
+ return undefined;
120
+ }
121
+ };
122
+ const isOwnedProcessId = (pid, userId) => Number.isSafeInteger(pid) && pid > 1 && isUserOwned(`/proc/${String(pid)}`, userId);
123
+ const addRuntimeArguments = (live, processArgs) => {
124
+ for (const argument of processArgs) {
125
+ const runtimeDir = runtimeFromArgument(argument);
126
+ if (runtimeDir !== undefined) {
127
+ live.add(runtimeDir);
128
+ }
129
+ }
130
+ };
131
+ const addProcessRuntimes = (live, pid) => {
132
+ const processArgs = readProcessArguments(pid);
133
+ if (processArgs === undefined) {
134
+ return;
135
+ }
136
+ addRuntimeArguments(live, processArgs);
137
+ const runtimeDir = runtimeFromEnvironment(pid);
138
+ if (runtimeDir !== undefined) {
139
+ live.add(runtimeDir);
140
+ }
141
+ };
142
+ const findLiveRuntimeDirectories = (userId) => {
143
+ const live = new Set();
144
+ const processEntries = readdirSync("/proc");
145
+ for (const entry of processEntries) {
146
+ const pid = Number(entry);
147
+ if (!isOwnedProcessId(pid, userId)) {
148
+ continue;
149
+ }
150
+ addProcessRuntimes(live, pid);
151
+ }
152
+ return live;
153
+ };
154
+ const classifyRuntimeDirectory = (name, userId, live) => {
155
+ const runtimeDir = join(RUNTIME_ROOT, name);
156
+ if (live.has(runtimeDir) ||
157
+ !isPrivateRuntimeDirectory(runtimeDir, userId) ||
158
+ !hasGeneratedRuntimeFiles(runtimeDir, userId)) {
159
+ return [];
160
+ }
161
+ const cleanupDirectory = cleanupDirectoryIdentity(runtimeDir);
162
+ return cleanupDirectory === undefined ? [] : [{ runtimeDir, cleanupDirectory }];
163
+ };
164
+ const findStaleHeadlessDisplays = () => {
165
+ const userId = currentUserId();
166
+ if (userId === undefined) {
167
+ return [];
168
+ }
169
+ const live = findLiveRuntimeDirectories(userId);
170
+ return readdirSync(RUNTIME_ROOT, { withFileTypes: true })
171
+ .filter((entry) => entry.isDirectory() && RUNTIME_DIRECTORY_PATTERN.test(entry.name))
172
+ .flatMap((entry) => classifyRuntimeDirectory(entry.name, userId, live));
173
+ };
174
+ const isSameCleanupDirectory = (left, right) => left?.device === right.device &&
175
+ left.inode === right.inode &&
176
+ left.userId === right.userId;
177
+ const isReapable = (candidate, userId, live) => !live.has(candidate.runtimeDir) &&
178
+ isSameCleanupDirectory(cleanupDirectoryIdentity(candidate.runtimeDir), candidate.cleanupDirectory) &&
179
+ isPrivateRuntimeDirectory(candidate.runtimeDir, userId) &&
180
+ hasGeneratedRuntimeFiles(candidate.runtimeDir, userId);
181
+ const didReapCandidate = (candidate, userId, live) => {
182
+ if (!isReapable(candidate, userId, live)) {
183
+ return false;
184
+ }
185
+ removeCleanupDirectory(candidate.cleanupDirectory);
186
+ return cleanupDirectoryIdentity(candidate.runtimeDir) === undefined;
187
+ };
188
+ const reapStaleHeadlessDisplays = (candidates = findStaleHeadlessDisplays()) => {
189
+ const userId = currentUserId();
190
+ if (userId === undefined) {
191
+ return [];
192
+ }
193
+ const live = findLiveRuntimeDirectories(userId);
194
+ const removed = [];
195
+ for (const candidate of candidates) {
196
+ if (didReapCandidate(candidate, userId, live)) {
197
+ removed.push(candidate.runtimeDir);
198
+ }
199
+ }
200
+ return removed;
201
+ };
202
+ const reapStaleHeadlessDisplaysAtStartup = () => {
203
+ const removed = reapStaleHeadlessDisplays();
204
+ if (removed.length === 0) {
205
+ return;
206
+ }
207
+ const noun = removed.length === 1 ? "directory" : "directories";
208
+ info(`removed stale headless runtime ${noun}: ${removed.join(", ")}`);
209
+ };
210
+ export { findStaleHeadlessDisplays, reapStaleHeadlessDisplays, reapStaleHeadlessDisplaysAtStartup, };
211
+ //# sourceMappingURL=reap-headless-displays.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reap-headless-displays.js","sourceRoot":"","sources":["../src/reap-headless-displays.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n info,\n removeCleanupDirectory,\n} from \"@gtkx/utils\";\nimport { constants, lstatSync, readdirSync, readFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport {\n createBusConfig,\n createHeadlessRuntimeMarker,\n HEADLESS_RUNTIME_MARKER,\n isSwayConfig,\n} from \"./headless-config.ts\";\n\ntype StaleHeadlessDisplay = {\n runtimeDir: string;\n cleanupDirectory: CleanupDirectoryIdentity;\n};\n\nconst RUNTIME_DIRECTORY_PATTERN = /^gtkx-xdg-[A-Za-z0-9]{6}$/;\nconst STOPPED_PROCESS_STATES: Set<string> = new Set([\"Z\", \"X\", \"x\"]);\nconst CONFIG_SIZE_LIMIT = 2048;\nconst MINIMUM_STALE_AGE_MS = 5000;\nconst RUNTIME_ROOT = tmpdir();\n\nconst currentUserId = (): number | undefined => {\n const getuid = process.getuid;\n\n return getuid === undefined ? undefined : getuid();\n};\n\nconst isUserOwned = (path: string, userId: number): boolean => {\n try {\n return lstatSync(path).uid === userId;\n } catch {\n return false;\n }\n};\n\nconst isPrivateRuntimeDirectory = (runtimeDir: string, userId: number): boolean => {\n try {\n const stat = lstatSync(runtimeDir);\n\n return stat.isDirectory() &&\n stat.uid === userId &&\n (stat.mode & 0o777) === 0o700 &&\n Date.now() - stat.mtimeMs >= MINIMUM_STALE_AGE_MS &&\n RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) &&\n dirname(runtimeDir) === RUNTIME_ROOT;\n } catch {\n return false;\n }\n};\n\nconst readOwnedFile = (\n path: string,\n runtimeDir: string,\n userId: number,\n requiredMode?: number,\n): string | undefined => {\n try {\n if (dirname(path) !== runtimeDir) {\n return undefined;\n }\n\n const contents = readFileSync(path, {\n encoding: \"utf8\",\n flag: constants.O_RDONLY | constants.O_NOFOLLOW,\n });\n const stat = lstatSync(path);\n\n return stat.isFile() &&\n stat.uid === userId &&\n stat.size < CONFIG_SIZE_LIMIT &&\n (requiredMode === undefined || (stat.mode & 0o777) === requiredMode)\n ? contents\n : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst hasOnlyBusConfig = (runtimeDir: string): boolean => {\n try {\n const entries = readdirSync(runtimeDir);\n\n return entries.length === 1 && entries[0] === \"session.conf\";\n } catch {\n return false;\n }\n};\n\nconst hasGeneratedRuntimeFiles = (runtimeDir: string, userId: number): boolean => {\n const bus = readOwnedFile(join(runtimeDir, \"session.conf\"), runtimeDir, userId);\n\n if (bus !== createBusConfig(join(runtimeDir, \"bus\"))) {\n return false;\n }\n\n const sway = readOwnedFile(join(runtimeDir, \"sway.conf\"), runtimeDir, userId);\n const marker = readOwnedFile(join(runtimeDir, HEADLESS_RUNTIME_MARKER), runtimeDir, userId, 0o600);\n\n return marker === createHeadlessRuntimeMarker(runtimeDir) ||\n (sway !== undefined && isSwayConfig(sway)) ||\n hasOnlyBusConfig(runtimeDir);\n};\n\nconst readProcessArguments = (pid: number): string[] | undefined => {\n try {\n const stat = readFileSync(`/proc/${String(pid)}/stat`, \"utf8\");\n const state = stat.slice(stat.lastIndexOf(\") \") + 2).split(\" \", 1)[0];\n\n if (state === undefined || STOPPED_PROCESS_STATES.has(state)) {\n return undefined;\n }\n\n return readFileSync(`/proc/${String(pid)}/cmdline`)\n .toString()\n .split(\"\\0\")\n .filter((argument) => argument.length > 0);\n } catch {\n return undefined;\n }\n};\n\nconst runtimeFromArgument = (argument: string): string | undefined => {\n const path = argument.startsWith(\"--config-file=\") ? argument.slice(\"--config-file=\".length) : argument;\n const name = basename(path);\n\n if (name !== \"sway.conf\" && name !== \"session.conf\") {\n return undefined;\n }\n\n const runtimeDir = dirname(path);\n\n return RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) && dirname(runtimeDir) === RUNTIME_ROOT\n ? runtimeDir\n : undefined;\n};\n\nconst runtimeFromEnvironment = (pid: number): string | undefined => {\n try {\n const prefix = \"XDG_RUNTIME_DIR=\";\n const entry = readFileSync(`/proc/${String(pid)}/environ`, \"utf8\")\n .split(\"\\0\")\n .find((value) => value.startsWith(prefix));\n const runtimeDir = entry?.slice(prefix.length);\n\n return runtimeDir !== undefined &&\n RUNTIME_DIRECTORY_PATTERN.test(basename(runtimeDir)) &&\n dirname(runtimeDir) === RUNTIME_ROOT\n ? runtimeDir\n : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst isOwnedProcessId = (pid: number, userId: number): boolean =>\n Number.isSafeInteger(pid) && pid > 1 && isUserOwned(`/proc/${String(pid)}`, userId);\n\nconst addRuntimeArguments = (live: Set<string>, processArgs: string[]): void => {\n for (const argument of processArgs) {\n const runtimeDir = runtimeFromArgument(argument);\n\n if (runtimeDir !== undefined) {\n live.add(runtimeDir);\n }\n }\n};\n\nconst addProcessRuntimes = (live: Set<string>, pid: number): void => {\n const processArgs = readProcessArguments(pid);\n\n if (processArgs === undefined) {\n return;\n }\n\n addRuntimeArguments(live, processArgs);\n const runtimeDir = runtimeFromEnvironment(pid);\n\n if (runtimeDir !== undefined) {\n live.add(runtimeDir);\n }\n};\n\nconst findLiveRuntimeDirectories = (userId: number): Set<string> => {\n const live: Set<string> = new Set();\n const processEntries = readdirSync(\"/proc\");\n\n for (const entry of processEntries) {\n const pid = Number(entry);\n\n if (!isOwnedProcessId(pid, userId)) {\n continue;\n }\n\n addProcessRuntimes(live, pid);\n }\n\n return live;\n};\n\nconst classifyRuntimeDirectory = (\n name: string,\n userId: number,\n live: ReadonlySet<string>,\n): StaleHeadlessDisplay[] => {\n const runtimeDir = join(RUNTIME_ROOT, name);\n\n if (\n live.has(runtimeDir) ||\n !isPrivateRuntimeDirectory(runtimeDir, userId) ||\n !hasGeneratedRuntimeFiles(runtimeDir, userId)\n ) {\n return [];\n }\n\n const cleanupDirectory = cleanupDirectoryIdentity(runtimeDir);\n\n return cleanupDirectory === undefined ? [] : [{ runtimeDir, cleanupDirectory }];\n};\n\nconst findStaleHeadlessDisplays = (): StaleHeadlessDisplay[] => {\n const userId = currentUserId();\n\n if (userId === undefined) {\n return [];\n }\n\n const live = findLiveRuntimeDirectories(userId);\n\n return readdirSync(RUNTIME_ROOT, { withFileTypes: true })\n .filter((entry) => entry.isDirectory() && RUNTIME_DIRECTORY_PATTERN.test(entry.name))\n .flatMap((entry) => classifyRuntimeDirectory(entry.name, userId, live));\n};\n\nconst isSameCleanupDirectory = (\n left: CleanupDirectoryIdentity | undefined,\n right: CleanupDirectoryIdentity,\n): boolean =>\n left?.device === right.device &&\n left.inode === right.inode &&\n left.userId === right.userId;\n\nconst isReapable = (\n candidate: StaleHeadlessDisplay,\n userId: number,\n live: ReadonlySet<string>,\n): boolean =>\n !live.has(candidate.runtimeDir) &&\n isSameCleanupDirectory(cleanupDirectoryIdentity(candidate.runtimeDir), candidate.cleanupDirectory) &&\n isPrivateRuntimeDirectory(candidate.runtimeDir, userId) &&\n hasGeneratedRuntimeFiles(candidate.runtimeDir, userId);\n\nconst didReapCandidate = (\n candidate: StaleHeadlessDisplay,\n userId: number,\n live: ReadonlySet<string>,\n): boolean => {\n if (!isReapable(candidate, userId, live)) {\n return false;\n }\n\n removeCleanupDirectory(candidate.cleanupDirectory);\n\n return cleanupDirectoryIdentity(candidate.runtimeDir) === undefined;\n};\n\nconst reapStaleHeadlessDisplays = (\n candidates: readonly StaleHeadlessDisplay[] = findStaleHeadlessDisplays(),\n): string[] => {\n const userId = currentUserId();\n\n if (userId === undefined) {\n return [];\n }\n\n const live = findLiveRuntimeDirectories(userId);\n const removed: string[] = [];\n\n for (const candidate of candidates) {\n if (didReapCandidate(candidate, userId, live)) {\n removed.push(candidate.runtimeDir);\n }\n }\n\n return removed;\n};\n\nconst reapStaleHeadlessDisplaysAtStartup = (): void => {\n const removed = reapStaleHeadlessDisplays();\n\n if (removed.length === 0) {\n return;\n }\n\n const noun = removed.length === 1 ? \"directory\" : \"directories\";\n info(`removed stale headless runtime ${noun}: ${removed.join(\", \")}`);\n};\n\nexport {\n findStaleHeadlessDisplays,\n reapStaleHeadlessDisplays,\n reapStaleHeadlessDisplaysAtStartup,\n type StaleHeadlessDisplay,\n};\n"]}
@@ -1,7 +1,11 @@
1
- import { installGracefulShutdown } from "@gtkx/utils";
1
+ import { installGracefulShutdown, watchParentProcess } from "@gtkx/utils";
2
+ import { isMainThread } from "node:worker_threads";
2
3
  import { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from "./headless-display.js";
3
- const options = readHeadlessOptions(new URL(import.meta.url).searchParams);
4
- const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));
5
- process.on("exit", teardown);
6
- installGracefulShutdown({ onSignal: teardown });
4
+ if (isMainThread) {
5
+ watchParentProcess();
6
+ const options = readHeadlessOptions(new URL(import.meta.url).searchParams);
7
+ const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));
8
+ process.on("exit", teardown);
9
+ installGracefulShutdown({ onSignal: teardown });
10
+ }
7
11
  //# sourceMappingURL=worker-preload.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker-preload.js","sourceRoot":"","sources":["../src/worker-preload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE1G,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;AAC3E,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;AAE7E,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC7B,uBAAuB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC","sourcesContent":["import { installGracefulShutdown } from \"@gtkx/utils\";\nimport { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from \"./headless-display.ts\";\n\nconst options = readHeadlessOptions(new URL(import.meta.url).searchParams);\nconst teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));\n\nprocess.on(\"exit\", teardown);\ninstallGracefulShutdown({ onSignal: teardown });\n"]}
1
+ {"version":3,"file":"worker-preload.js","sourceRoot":"","sources":["../src/worker-preload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE1G,IAAI,YAAY,EAAE,CAAC;IACf,kBAAkB,EAAE,CAAC;IAErB,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;IAE7E,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7B,uBAAuB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;AACpD,CAAC","sourcesContent":["import { installGracefulShutdown, watchParentProcess } from \"@gtkx/utils\";\nimport { isMainThread } from \"node:worker_threads\";\nimport { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from \"./headless-display.ts\";\n\nif (isMainThread) {\n watchParentProcess();\n\n const options = readHeadlessOptions(new URL(import.meta.url).searchParams);\n const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));\n\n process.on(\"exit\", teardown);\n installGracefulShutdown({ onSignal: teardown });\n}\n"]}