@gtkx/vitest 1.0.0-rc.3 → 1.0.0

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/README.md CHANGED
@@ -6,7 +6,8 @@
6
6
 
7
7
  <p align="center">
8
8
  The React framework for Linux.<br />
9
- Build GTK4 and Adwaita apps in TypeScript, with React components and hooks driving GNOME's own widgets. What you ship is a GNOME app.
9
+ Write native Linux applications with React and TypeScript.
10
+ Built on the GNOME stack and standard web tooling.
10
11
  </p>
11
12
 
12
13
  <p align="center">
@@ -20,15 +21,13 @@
20
21
 
21
22
  <p align="center">
22
23
  <a href="https://gtkx.dev">Homepage</a> &middot;
23
- <a href="https://gtkx.dev/guide/why-gtkx">Documentation</a> &middot;
24
+ <a href="https://gtkx.dev/guide/getting-started">Documentation</a> &middot;
24
25
  <a href="https://github.com/gtkx-org/gtkx/tree/main/examples">Examples</a> &middot;
25
26
  <a href="https://github.com/gtkx-org/gtkx/blob/main/CONTRIBUTING.md">Contributing</a>
26
27
  </p>
27
28
 
28
29
  ---
29
30
 
30
- GTKX generates fully typed bindings for the entire GTK4 and Adwaita surface directly from GObject-Introspection. On top of those bindings you get the React programming model: components and hooks driving GObject instances, with Fast Refresh while you develop.
31
-
32
31
  <p align="center">
33
32
  <img src="https://raw.githubusercontent.com/gtkx-org/gtkx/main/examples/tutorial/assets/screenshot.png" alt="The Tasks app: an Adwaita window with a sidebar of smart views and colored lists on the left, and a boxed task list on the right." />
34
33
  </p>
@@ -109,7 +108,7 @@ React Native and similar frameworks hide the native toolkit so one API can run e
109
108
 
110
109
  ### Why Node.js, and why generated bindings
111
110
 
112
- GTKX runs on Node.js, which puts native modules, the npm ecosystem, and the tooling built for Node.js APIs within reach. GJS is GNOME's own runtime, built on SpiderMonkey rather than V8; node-gtk runs on Node.js but is lightly maintained, on the older nan/V8 ABI rather than N-API, and still centered on GTK3. The [why-gtkx guide](https://gtkx.dev/guide/why-gtkx) covers the comparison in full.
111
+ GTKX runs on Node.js, which puts native modules, the npm ecosystem, and the tooling built for Node.js APIs within reach. GJS is GNOME's own runtime, built on SpiderMonkey rather than V8; node-gtk runs on Node.js but is lightly maintained, on the older nan/V8 ABI rather than N-API, and still centered on GTK3. The [Why GTKX guide](https://gtkx.dev/guide/why-gtkx) covers what running on Node.js means day to day.
113
112
 
114
113
  GTKX generates the TypeScript types and the native FFI calls from the same GObject-Introspection data, so the types cannot drift from the calls they back. Codegen covers the whole GTK4 and Adwaita surface.
115
114
 
@@ -122,10 +121,10 @@ GTKX is Linux-only and needs Node.js 24 or later. See [Requirements](#requiremen
122
121
  Scaffold a new app with the `create-gtkx` initializer:
123
122
 
124
123
  ```sh
125
- npm create gtkx@rc
124
+ npm create gtkx
126
125
  ```
127
126
 
128
- The same command works with other package managers: `pnpm create gtkx@rc` or `yarn create gtkx@rc`.
127
+ The same command works with other package managers: `pnpm create gtkx` or `yarn create gtkx`.
129
128
 
130
129
  Then run your new app:
131
130
 
@@ -146,7 +145,7 @@ The documentation at **[gtkx.dev](https://gtkx.dev)** includes a step-by-step tu
146
145
 
147
146
  GTKX is Linux-only. You need:
148
147
 
149
- - Linux with the GTK4 (4.20 or later), Adwaita (1.8 or later), and GLib development libraries
148
+ - Linux with the GTK4 (4.20 or later) and GLib development libraries, plus Adwaita (1.8 or later) once your project binds `Adw-1`
150
149
  - Node.js 24 or later
151
150
 
152
151
  The `@gtkx/native` addon ships prebuilt for x64 and arm64 glibc Linux; other targets need to build it from the GTKX repository, which requires a Rust toolchain.
@@ -162,7 +161,7 @@ Explore the [example apps](https://github.com/gtkx-org/gtkx/tree/main/examples):
162
161
 
163
162
  ## Status
164
163
 
165
- GTKX 1.0 is at the release candidate stage.
164
+ GTKX 1.0 is released.
166
165
 
167
166
  ## Contributing
168
167
 
@@ -1 +1 @@
1
- {"version":3,"file":"headless-display.d.ts","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,KAAK,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEtC;;GAEG;AACH,KAAK,eAAe,GAAG;IACnB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,UAAU,EAAE,YAAY,CAAC;CAC5B,CAAC;AAgDF,QAAA,MAAM,qBAAqB,aAAa,CAAC;AA6DzC,QAAA,MAAM,mBAAmB;;;;;;;;;;;CAWxB,CAAC;AAEF,QAAA,MAAM,sBAAsB,GAAI,UAAU,OAAO,CAAC,eAAe,CAAC,KAAG,eAGnE,CAAC;AAiDH,QAAA,MAAM,mBAAmB,GAAI,QAAQ,eAAe,KAAG,OAAO,CAAC,eAAe,CAe7E,CAAC;AA4OF,QAAA,MAAM,oBAAoB,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,MAAM,IAAI,CAiDhF,CAAC;AAEF,OAAO,EACH,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,eAAe,GACvB,CAAC"}
1
+ {"version":3,"file":"headless-display.d.ts","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,KAAK,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEtC;;GAEG;AACH,KAAK,eAAe,GAAG;IACnB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,UAAU,EAAE,YAAY,CAAC;CAC5B,CAAC;AAgDF,QAAA,MAAM,qBAAqB,aAAa,CAAC;AA4DzC,QAAA,MAAM,mBAAmB;;;;;;;;;;;CAWxB,CAAC;AAEF,QAAA,MAAM,sBAAsB,GAAI,UAAU,OAAO,CAAC,eAAe,CAAC,KAAG,eAGnE,CAAC;AAqCH,QAAA,MAAM,mBAAmB,GAAI,QAAQ,eAAe,KAAG,OAAO,CAAC,eAAe,CAe7E,CAAC;AA4OF,QAAA,MAAM,oBAAoB,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,MAAM,IAAI,CAiDhF,CAAC;AAEF,OAAO,EACH,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,eAAe,GACvB,CAAC"}
@@ -1,21 +1,20 @@
1
- import { spawn, spawnSync } from "node:child_process";
1
+ import { resolveExecutable, spawnWithParentDeathSignal } from "@gtkx/utils";
2
+ import { spawnSync } from "node:child_process";
2
3
  import { chmodSync, createWriteStream, existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
4
  import { Socket } from "node:net";
4
5
  import { tmpdir } from "node:os";
5
6
  import { join } from "node:path";
6
7
  import { startNotificationService } from "./notification-service.js";
7
- import { resolveExecutable } from "./resolve-executable.js";
8
8
  import { startVirtualSeat } from "./virtual-seat.js";
9
9
  const DEFAULT_HEADLESS_SIZE = "1024x768";
10
10
  const DEFAULT_HEADLESS_COMPOSITOR = "sway";
11
11
  const BUS_CONFIG_DOCTYPE = '<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" ' +
12
12
  '"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">';
13
- const PARENT_DEATH_SCRIPT = 'trap \'kill -9 "$child" 2>/dev/null\' TERM; "$@" & child=$!; wait "$child"';
14
13
  const hasWestonFakeSeat = createWestonFakeSeatProbe();
15
14
  const compositorRegistry = {
16
15
  sway: {
17
16
  socket: "wayland-1",
18
- needsVirtualSeat: true,
17
+ requiresVirtualSeat: true,
19
18
  env: {
20
19
  WLR_BACKENDS: "headless",
21
20
  WLR_RENDERER: "pixman",
@@ -35,12 +34,12 @@ const compositorRegistry = {
35
34
  'for_window [title=".*"] floating enable, border none',
36
35
  "",
37
36
  ].join("\n"));
38
- return spawnWithParentDeathSignal("sway", ["-c", configPath], ["ignore", "ignore", "pipe"]);
37
+ return spawnWithParentDeathSignal("sway", ["-c", configPath], { stdio: ["ignore", "ignore", "pipe"] });
39
38
  },
40
39
  },
41
40
  weston: {
42
41
  socket: "wayland-0",
43
- needsVirtualSeat: false,
42
+ requiresVirtualSeat: false,
44
43
  env: {},
45
44
  start: (_runtimeDir, width, height) => spawnWithParentDeathSignal("weston", [
46
45
  "--backend=headless",
@@ -49,7 +48,7 @@ const compositorRegistry = {
49
48
  `--width=${width}`,
50
49
  `--height=${height}`,
51
50
  "--socket=wayland-0",
52
- ], ["ignore", "ignore", "pipe"]),
51
+ ], { stdio: ["ignore", "ignore", "pipe"] }),
53
52
  },
54
53
  };
55
54
  const STATIC_HEADLESS_ENV = {
@@ -86,11 +85,6 @@ const restoreEnv = (snapshot) => {
86
85
  }
87
86
  }
88
87
  };
89
- const spawnWithParentDeathSignal = (command, args, stdio) => {
90
- const child = spawn(resolveExecutable("setpriv"), ["--pdeathsig", "SIGTERM", "sh", "-c", PARENT_DEATH_SCRIPT, "sh", command, ...args], { stdio });
91
- child.unref();
92
- return child;
93
- };
94
88
  function createWestonFakeSeatProbe() {
95
89
  let isSupported;
96
90
  return () => {
@@ -121,11 +115,11 @@ const startCompositor = (runtimeDir, options, env) => {
121
115
  return {
122
116
  child: descriptor.start(runtimeDir, width, height),
123
117
  socket: descriptor.socket,
124
- needsVirtualSeat: descriptor.needsVirtualSeat,
118
+ requiresVirtualSeat: descriptor.requiresVirtualSeat,
125
119
  };
126
120
  };
127
121
  const noVirtualSeat = () => undefined;
128
- const attachVirtualSeat = (compositor, socketPath) => compositor.needsVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);
122
+ const attachVirtualSeat = (compositor, socketPath) => compositor.requiresVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);
129
123
  const writeBusConfig = (busConfigPath, busSocketPath) => {
130
124
  writeFileSync(busConfigPath, [
131
125
  BUS_CONFIG_DOCTYPE,
@@ -160,10 +154,10 @@ const captureStderr = (child) => {
160
154
  };
161
155
  };
162
156
  const trackChild = (child, handlers) => {
163
- child.on("exit", handlers.exit);
157
+ child.on("close", handlers.exit);
164
158
  child.on("error", handlers.error);
165
159
  return () => {
166
- child.removeListener("exit", handlers.exit);
160
+ child.removeListener("close", handlers.exit);
167
161
  child.removeListener("error", handlers.error);
168
162
  };
169
163
  };
@@ -303,10 +297,14 @@ const startHeadlessDisplay = async (options) => {
303
297
  const busConfigPath = join(runtimeDir, "session.conf");
304
298
  const busSocketPath = join(runtimeDir, "bus");
305
299
  writeBusConfig(busConfigPath, busSocketPath);
306
- const busChild = spawnWithParentDeathSignal("dbus-daemon", [`--config-file=${busConfigPath}`], ["ignore", "ignore", "pipe"]);
300
+ const busChild = spawnWithParentDeathSignal("dbus-daemon", [`--config-file=${busConfigPath}`], {
301
+ stdio: ["ignore", "ignore", "pipe"],
302
+ });
303
+ busChild.unref();
307
304
  spawned.push(busChild);
308
305
  applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });
309
306
  const compositor = startCompositor(runtimeDir, options, env);
307
+ compositor.child.unref();
310
308
  spawned.push(compositor.child);
311
309
  applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });
312
310
  const compositorSocketPath = join(runtimeDir, compositor.socket);
@@ -1 +1 @@
1
- {"version":3,"file":"headless-display.js","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn, spawnSync, type StdioOptions } 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.js\";\nimport { resolveExecutable } from \"./resolve-executable.js\";\nimport { startVirtualSeat } from \"./virtual-seat.js\";\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 needsVirtualSeat: boolean;\n start: (runtimeDir: string, width: string, height: string) => ChildProcess;\n};\n\ntype SpawnedCompositor = {\n child: ChildProcess;\n socket: string;\n needsVirtualSeat: boolean;\n};\n\ntype WaitForSocketOptions = {\n label: string;\n timeout?: number;\n child?: ChildProcess;\n signal?: AbortSignal;\n};\n\ntype StderrCapture = {\n read: () => string;\n stop: () => void;\n};\n\ntype ChildHandlers = {\n exit: (code: number | null, signal: NodeJS.Signals | null) => void;\n error: (cause: Error) => void;\n};\n\ntype DisplaySockets = {\n compositor: SpawnedCompositor;\n compositorSocketPath: string;\n busChild: ChildProcess;\n busSocketPath: string;\n};\n\ntype SocketWatch = {\n path: string;\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 PARENT_DEATH_SCRIPT = 'trap \\'kill -9 \"$child\" 2>/dev/null\\' TERM; \"$@\" & child=$!; wait \"$child\"';\nconst hasWestonFakeSeat = createWestonFakeSeatProbe();\n\nconst compositorRegistry: Record<CompositorId, CompositorDescriptor> = {\n sway: {\n socket: \"wayland-1\",\n needsVirtualSeat: 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], [\"ignore\", \"ignore\", \"pipe\"]);\n },\n },\n weston: {\n socket: \"wayland-0\",\n needsVirtualSeat: 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 [\"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\nconst spawnWithParentDeathSignal = (command: string, args: string[], stdio: StdioOptions): ChildProcess => {\n const child = spawn(\n resolveExecutable(\"setpriv\"),\n [\"--pdeathsig\", \"SIGTERM\", \"sh\", \"-c\", PARENT_DEATH_SCRIPT, \"sh\", command, ...args],\n { stdio },\n );\n\n child.unref();\n\n return child;\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 needsVirtualSeat: descriptor.needsVirtualSeat,\n };\n};\n\nconst noVirtualSeat = (): void => undefined;\n\nconst attachVirtualSeat = (compositor: SpawnedCompositor, socketPath: string): Promise<() => void> =>\n compositor.needsVirtualSeat ? 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 captureStderr = (child: ChildProcess | undefined): StderrCapture => {\n const stderr = child?.stderr ?? null;\n let log = \"\";\n stderr?.setEncoding(\"utf8\");\n\n stderr?.on(\"data\", (chunk: string) => {\n log += chunk;\n });\n\n return {\n read: () => log,\n stop: () => {\n stderr?.removeAllListeners(\"data\");\n stderr?.resume();\n\n if (stderr instanceof Socket) {\n stderr.unref();\n }\n },\n };\n};\n\nconst trackChild = (child: ChildProcess, handlers: ChildHandlers): (() => void) => {\n child.on(\"exit\", handlers.exit);\n child.on(\"error\", handlers.error);\n\n return () => {\n child.removeListener(\"exit\", handlers.exit);\n child.removeListener(\"error\", handlers.error);\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 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 stderrSuffix = (child: ChildProcess | undefined, stderr: StderrCapture): string =>\n child ? `\\n${stderr.read()}` : \"\";\n\nconst hasAlreadyAborted = (signal: AbortSignal | undefined, onAbort: () => void, cleanups: (() => void)[]): boolean => {\n if (signal === undefined) {\n return false;\n }\n\n if (signal.aborted) {\n return true;\n }\n\n signal.addEventListener(\"abort\", onAbort);\n\n cleanups.push(() => {\n signal.removeEventListener(\"abort\", onAbort);\n });\n\n return false;\n};\n\nconst watchForSocket = ({ path, options, resolve, reject }: SocketWatch): void => {\n const { label, timeout = 15_000, child, signal } = options;\n const stderr = captureStderr(child);\n const cleanups: (() => void)[] = [stderr.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 onExit = (code: number | null, terminationSignal: NodeJS.Signals | null): void => {\n fail(`${exitedMessage(label, path, code, terminationSignal)}\\n${stderr.read()}`);\n };\n\n const onError = (cause: Error): void => {\n fail(`${label} failed to spawn: ${cause.message}\\n${stderr.read()}`);\n };\n\n const onAbort = (): void => {\n fail(`${label} startup aborted before ${path} appeared`);\n };\n\n const poll = pollForPath(path, () => {\n stop();\n resolve();\n });\n\n const timer = setTimeout(() => {\n fail(`${label} did not become available within ${String(timeout)}ms${stderrSuffix(child, stderr)}`);\n }, timeout);\n\n cleanups.push(() => {\n clearInterval(poll);\n clearTimeout(timer);\n });\n\n if (child) {\n cleanups.push(trackChild(child, { exit: onExit, error: onError }));\n }\n\n if (hasAlreadyAborted(signal, onAbort, cleanups)) {\n onAbort();\n }\n};\n\nconst waitForSocket = (path: string, options: WaitForSocketOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n watchForSocket({ path, 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 (sockets: DisplaySockets): Promise<void> => {\n const { compositor, compositorSocketPath, busChild, busSocketPath } = sockets;\n const abort = new AbortController();\n\n try {\n await Promise.all([\n waitForSocket(compositorSocketPath, {\n label: \"Compositor\",\n child: compositor.child,\n signal: abort.signal,\n }),\n waitForSocket(busSocketPath, { label: \"D-Bus session bus\", child: busChild, signal: abort.signal }),\n ]);\n } finally {\n abort.abort();\n }\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(\n \"dbus-daemon\",\n [`--config-file=${busConfigPath}`],\n [\"ignore\", \"ignore\", \"pipe\"],\n );\n\n spawned.push(busChild);\n applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });\n const compositor = startCompositor(runtimeDir, options, env);\n spawned.push(compositor.child);\n applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n const compositorSocketPath = join(runtimeDir, compositor.socket);\n await waitForDisplaySockets({ compositor, compositorSocketPath, busChild, busSocketPath });\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 DEFAULT_HEADLESS_SIZE,\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":"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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 WaitForSocketOptions = {\n label: string;\n timeout?: number;\n child?: ChildProcess;\n signal?: AbortSignal;\n};\n\ntype StderrCapture = {\n read: () => string;\n stop: () => void;\n};\n\ntype ChildHandlers = {\n exit: (code: number | null, signal: NodeJS.Signals | null) => void;\n error: (cause: Error) => void;\n};\n\ntype DisplaySockets = {\n compositor: SpawnedCompositor;\n compositorSocketPath: string;\n busChild: ChildProcess;\n busSocketPath: string;\n};\n\ntype SocketWatch = {\n path: string;\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 captureStderr = (child: ChildProcess | undefined): StderrCapture => {\n const stderr = child?.stderr ?? null;\n let log = \"\";\n stderr?.setEncoding(\"utf8\");\n\n stderr?.on(\"data\", (chunk: string) => {\n log += chunk;\n });\n\n return {\n read: () => log,\n stop: () => {\n stderr?.removeAllListeners(\"data\");\n stderr?.resume();\n\n if (stderr instanceof Socket) {\n stderr.unref();\n }\n },\n };\n};\n\nconst trackChild = (child: ChildProcess, handlers: ChildHandlers): (() => void) => {\n child.on(\"close\", handlers.exit);\n child.on(\"error\", handlers.error);\n\n return () => {\n child.removeListener(\"close\", handlers.exit);\n child.removeListener(\"error\", handlers.error);\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 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 stderrSuffix = (child: ChildProcess | undefined, stderr: StderrCapture): string =>\n child ? `\\n${stderr.read()}` : \"\";\n\nconst hasAlreadyAborted = (signal: AbortSignal | undefined, onAbort: () => void, cleanups: (() => void)[]): boolean => {\n if (signal === undefined) {\n return false;\n }\n\n if (signal.aborted) {\n return true;\n }\n\n signal.addEventListener(\"abort\", onAbort);\n\n cleanups.push(() => {\n signal.removeEventListener(\"abort\", onAbort);\n });\n\n return false;\n};\n\nconst watchForSocket = ({ path, options, resolve, reject }: SocketWatch): void => {\n const { label, timeout = 15_000, child, signal } = options;\n const stderr = captureStderr(child);\n const cleanups: (() => void)[] = [stderr.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 onExit = (code: number | null, terminationSignal: NodeJS.Signals | null): void => {\n fail(`${exitedMessage(label, path, code, terminationSignal)}\\n${stderr.read()}`);\n };\n\n const onError = (cause: Error): void => {\n fail(`${label} failed to spawn: ${cause.message}\\n${stderr.read()}`);\n };\n\n const onAbort = (): void => {\n fail(`${label} startup aborted before ${path} appeared`);\n };\n\n const poll = pollForPath(path, () => {\n stop();\n resolve();\n });\n\n const timer = setTimeout(() => {\n fail(`${label} did not become available within ${String(timeout)}ms${stderrSuffix(child, stderr)}`);\n }, timeout);\n\n cleanups.push(() => {\n clearInterval(poll);\n clearTimeout(timer);\n });\n\n if (child) {\n cleanups.push(trackChild(child, { exit: onExit, error: onError }));\n }\n\n if (hasAlreadyAborted(signal, onAbort, cleanups)) {\n onAbort();\n }\n};\n\nconst waitForSocket = (path: string, options: WaitForSocketOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n watchForSocket({ path, 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 (sockets: DisplaySockets): Promise<void> => {\n const { compositor, compositorSocketPath, busChild, busSocketPath } = sockets;\n const abort = new AbortController();\n\n try {\n await Promise.all([\n waitForSocket(compositorSocketPath, {\n label: \"Compositor\",\n child: compositor.child,\n signal: abort.signal,\n }),\n waitForSocket(busSocketPath, { label: \"D-Bus session bus\", child: busChild, signal: abort.signal }),\n ]);\n } finally {\n abort.abort();\n }\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 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 applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n const compositorSocketPath = join(runtimeDir, compositor.socket);\n await waitForDisplaySockets({ compositor, compositorSocketPath, busChild, busSocketPath });\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 DEFAULT_HEADLESS_SIZE,\n STATIC_HEADLESS_ENV,\n resolveHeadlessOptions,\n readHeadlessOptions,\n startHeadlessDisplay,\n type CompositorId,\n type HeadlessOptions,\n};\n"]}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { Plugin } from "vitest/config";
2
- import { type HeadlessOptions } from "./headless-display.js";
2
+ import { type HeadlessOptions } from "./headless-display.ts";
3
3
  /**
4
- * Options accepted by the {@link gtkx} Vitest plugin. Every headless display
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 GtkxPluginOptions = Partial<HeadlessOptions>;
7
+ type PluginOptions = Partial<HeadlessOptions>;
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
@@ -13,8 +13,8 @@ type GtkxPluginOptions = Partial<HeadlessOptions>;
13
13
  * @param options Headless display settings (size, compositor) forwarded to each worker.
14
14
  * @returns A Vitest config plugin.
15
15
  */
16
- declare const gtkx: (options?: GtkxPluginOptions) => Plugin;
16
+ declare const gtkx: (options?: PluginOptions) => Plugin;
17
17
  export default gtkx;
18
- export { type CompositorId, type HeadlessOptions } from "./headless-display.js";
19
- export { type GtkxPluginOptions };
18
+ export { type CompositorId, type HeadlessOptions } from "./headless-display.ts";
19
+ export { type PluginOptions };
20
20
  //# sourceMappingURL=index.d.ts.map
@@ -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,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;AA2BlD;;;;;;;GAOG;AACH,QAAA,MAAM,IAAI,GAAI,UAAS,iBAAsB,KAAG,MAqB1C,CAAC;AAEP,eAAe,IAAI,CAAC;AACpB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,KAAK,iBAAiB,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;AAK5C,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;AAElF;;;GAGG;AACH,KAAK,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;AAqB9C;;;;;;;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"}
package/dist/index.js CHANGED
@@ -16,10 +16,6 @@ const headlessPreloadSpecifier = (options) => {
16
16
  }
17
17
  return url.href;
18
18
  };
19
- const workerSetupPath = () => {
20
- const sibling = join(import.meta.dirname, "worker-setup.js");
21
- return existsSync(sibling) ? sibling : join(import.meta.dirname, "..", "dist", "worker-setup.js");
22
- };
23
19
  /**
24
20
  * Vitest plugin that runs each test worker against its own isolated headless
25
21
  * Wayland display. It configures the forks pool, injects the worker preload and
@@ -35,7 +31,6 @@ const gtkx = (options = {}) => createConfigPlugin({
35
31
  test: {
36
32
  globals: true,
37
33
  execArgv: ["--import", headlessPreloadSpecifier(options)],
38
- setupFiles: [workerSetupPath()],
39
34
  testTimeout: 30_000,
40
35
  hookTimeout: 30_000,
41
36
  pool: "forks",
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;AAE7E,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,OAA0B,EAAU,EAAE;IACpE,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,MAAM,eAAe,GAAG,GAAW,EAAE;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAE7D,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC;AACtG,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,IAAI,GAAG,CAAC,UAA6B,EAAE,EAAU,EAAE,CACrD,kBAAkB,CAAC;IACf,IAAI,EAAE,aAAa;IACnB,MAAM;QACF,OAAO;YACH,IAAI,EAAE;gBACF,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,CAAC,UAAU,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;gBACzD,UAAU,EAAE,CAAC,eAAe,EAAE,CAAC;gBAC/B,WAAW,EAAE,MAAM;gBACnB,WAAW,EAAE,MAAM;gBACnB,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,EAA0B,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.js\";\n\n/**\n * Options accepted by the {@link gtkx} Vitest plugin. Every headless display\n * setting is optional and falls back to a built-in default when omitted.\n */\ntype GtkxPluginOptions = Partial<HeadlessOptions>;\n\nconst GTKX_INLINE_DEPS: RegExp[] = [/@gtkx\\/(?!native)/, /[/\\\\]\\.gtkx[/\\\\]/];\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: GtkxPluginOptions): 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\nconst workerSetupPath = (): string => {\n const sibling = join(import.meta.dirname, \"worker-setup.js\");\n\n return existsSync(sibling) ? sibling : join(import.meta.dirname, \"..\", \"dist\", \"worker-setup.js\");\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: GtkxPluginOptions = {}): Plugin =>\n createConfigPlugin({\n name: \"gtkx:vitest\",\n config() {\n return {\n test: {\n globals: true,\n execArgv: [\"--import\", headlessPreloadSpecifier(options)],\n setupFiles: [workerSetupPath()],\n testTimeout: 30_000,\n hookTimeout: 30_000,\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.js\";\nexport { type GtkxPluginOptions };\n"]}
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;AAE7E,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;QACF,OAAO;YACH,IAAI,EAAE;gBACF,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,CAAC,UAAU,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;gBACzD,WAAW,EAAE,MAAM;gBACnB,WAAW,EAAE,MAAM;gBACnB,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[/\\\\]/];\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() {\n return {\n test: {\n globals: true,\n execArgv: [\"--import\", headlessPreloadSpecifier(options)],\n testTimeout: 30_000,\n hookTimeout: 30_000,\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,7 +1,7 @@
1
+ import { installGracefulShutdown } from "@gtkx/utils";
1
2
  import { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from "./headless-display.js";
2
- import { setHeadlessTeardown } from "./headless-globals.js";
3
3
  const options = readHeadlessOptions(new URL(import.meta.url).searchParams);
4
4
  const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));
5
5
  process.on("exit", teardown);
6
- setHeadlessTeardown(teardown);
6
+ installGracefulShutdown({ onSignal: teardown });
7
7
  //# 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,mBAAmB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC1G,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,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,mBAAmB,CAAC,QAAQ,CAAC,CAAC","sourcesContent":["import { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from \"./headless-display.js\";\nimport { setHeadlessTeardown } from \"./headless-globals.js\";\n\nconst options = readHeadlessOptions(new URL(import.meta.url).searchParams);\nconst teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));\n\nprocess.on(\"exit\", teardown);\nsetHeadlessTeardown(teardown);\n"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/vitest",
3
- "version": "1.0.0-rc.3",
3
+ "version": "1.0.0",
4
4
  "description": "Vitest plugin for GTKX applications with headless Wayland display isolation",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -42,8 +42,8 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@homebridge/dbus-native": "^0.7.8",
45
- "@gtkx/config": "1.0.0-rc.3",
46
- "@gtkx/utils": "1.0.0-rc.3"
45
+ "@gtkx/config": "1.0.0",
46
+ "@gtkx/utils": "1.0.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "vitest": "^4.1.10"
@@ -1,11 +1,11 @@
1
- import { type ChildProcess, spawn, spawnSync, type StdioOptions } from "node:child_process";
1
+ import { resolveExecutable, spawnWithParentDeathSignal } from "@gtkx/utils";
2
+ import { type ChildProcess, spawnSync } from "node:child_process";
2
3
  import { chmodSync, createWriteStream, existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
4
  import { Socket } from "node:net";
4
5
  import { tmpdir } from "node:os";
5
6
  import { join } from "node:path";
6
- import { startNotificationService } from "./notification-service.js";
7
- import { resolveExecutable } from "./resolve-executable.js";
8
- import { startVirtualSeat } from "./virtual-seat.js";
7
+ import { startNotificationService } from "./notification-service.ts";
8
+ import { startVirtualSeat } from "./virtual-seat.ts";
9
9
 
10
10
  /**
11
11
  * Wayland compositors that can back a headless display.
@@ -27,14 +27,14 @@ type EnvSnapshot = Record<string, string | undefined>;
27
27
  type CompositorDescriptor = {
28
28
  socket: string;
29
29
  env: Record<string, string>;
30
- needsVirtualSeat: boolean;
30
+ requiresVirtualSeat: boolean;
31
31
  start: (runtimeDir: string, width: string, height: string) => ChildProcess;
32
32
  };
33
33
 
34
34
  type SpawnedCompositor = {
35
35
  child: ChildProcess;
36
36
  socket: string;
37
- needsVirtualSeat: boolean;
37
+ requiresVirtualSeat: boolean;
38
38
  };
39
39
 
40
40
  type WaitForSocketOptions = {
@@ -75,13 +75,12 @@ const BUS_CONFIG_DOCTYPE =
75
75
  '<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" ' +
76
76
  '"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">';
77
77
 
78
- const PARENT_DEATH_SCRIPT = 'trap \'kill -9 "$child" 2>/dev/null\' TERM; "$@" & child=$!; wait "$child"';
79
78
  const hasWestonFakeSeat = createWestonFakeSeatProbe();
80
79
 
81
80
  const compositorRegistry: Record<CompositorId, CompositorDescriptor> = {
82
81
  sway: {
83
82
  socket: "wayland-1",
84
- needsVirtualSeat: true,
83
+ requiresVirtualSeat: true,
85
84
  env: {
86
85
  WLR_BACKENDS: "headless",
87
86
  WLR_RENDERER: "pixman",
@@ -106,12 +105,12 @@ const compositorRegistry: Record<CompositorId, CompositorDescriptor> = {
106
105
  ].join("\n"),
107
106
  );
108
107
 
109
- return spawnWithParentDeathSignal("sway", ["-c", configPath], ["ignore", "ignore", "pipe"]);
108
+ return spawnWithParentDeathSignal("sway", ["-c", configPath], { stdio: ["ignore", "ignore", "pipe"] });
110
109
  },
111
110
  },
112
111
  weston: {
113
112
  socket: "wayland-0",
114
- needsVirtualSeat: false,
113
+ requiresVirtualSeat: false,
115
114
  env: {},
116
115
  start: (_runtimeDir, width, height) =>
117
116
  spawnWithParentDeathSignal(
@@ -124,7 +123,7 @@ const compositorRegistry: Record<CompositorId, CompositorDescriptor> = {
124
123
  `--height=${height}`,
125
124
  "--socket=wayland-0",
126
125
  ],
127
- ["ignore", "ignore", "pipe"],
126
+ { stdio: ["ignore", "ignore", "pipe"] },
128
127
  ),
129
128
  },
130
129
  };
@@ -167,18 +166,6 @@ const restoreEnv = (snapshot: EnvSnapshot): void => {
167
166
  }
168
167
  };
169
168
 
170
- const spawnWithParentDeathSignal = (command: string, args: string[], stdio: StdioOptions): ChildProcess => {
171
- const child = spawn(
172
- resolveExecutable("setpriv"),
173
- ["--pdeathsig", "SIGTERM", "sh", "-c", PARENT_DEATH_SCRIPT, "sh", command, ...args],
174
- { stdio },
175
- );
176
-
177
- child.unref();
178
-
179
- return child;
180
- };
181
-
182
169
  function createWestonFakeSeatProbe(): () => boolean {
183
170
  let isSupported: boolean | undefined;
184
171
 
@@ -219,14 +206,14 @@ const startCompositor = (runtimeDir: string, options: HeadlessOptions, env: EnvS
219
206
  return {
220
207
  child: descriptor.start(runtimeDir, width, height),
221
208
  socket: descriptor.socket,
222
- needsVirtualSeat: descriptor.needsVirtualSeat,
209
+ requiresVirtualSeat: descriptor.requiresVirtualSeat,
223
210
  };
224
211
  };
225
212
 
226
213
  const noVirtualSeat = (): void => undefined;
227
214
 
228
215
  const attachVirtualSeat = (compositor: SpawnedCompositor, socketPath: string): Promise<() => void> =>
229
- compositor.needsVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);
216
+ compositor.requiresVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);
230
217
 
231
218
  const writeBusConfig = (busConfigPath: string, busSocketPath: string): void => {
232
219
  writeFileSync(
@@ -270,11 +257,11 @@ const captureStderr = (child: ChildProcess | undefined): StderrCapture => {
270
257
  };
271
258
 
272
259
  const trackChild = (child: ChildProcess, handlers: ChildHandlers): (() => void) => {
273
- child.on("exit", handlers.exit);
260
+ child.on("close", handlers.exit);
274
261
  child.on("error", handlers.error);
275
262
 
276
263
  return () => {
277
- child.removeListener("exit", handlers.exit);
264
+ child.removeListener("close", handlers.exit);
278
265
  child.removeListener("error", handlers.error);
279
266
  };
280
267
  };
@@ -462,15 +449,15 @@ const startHeadlessDisplay = async (options: HeadlessOptions): Promise<() => voi
462
449
  const busSocketPath = join(runtimeDir, "bus");
463
450
  writeBusConfig(busConfigPath, busSocketPath);
464
451
 
465
- const busChild = spawnWithParentDeathSignal(
466
- "dbus-daemon",
467
- [`--config-file=${busConfigPath}`],
468
- ["ignore", "ignore", "pipe"],
469
- );
452
+ const busChild = spawnWithParentDeathSignal("dbus-daemon", [`--config-file=${busConfigPath}`], {
453
+ stdio: ["ignore", "ignore", "pipe"],
454
+ });
470
455
 
456
+ busChild.unref();
471
457
  spawned.push(busChild);
472
458
  applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });
473
459
  const compositor = startCompositor(runtimeDir, options, env);
460
+ compositor.child.unref();
474
461
  spawned.push(compositor.child);
475
462
  applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });
476
463
  const compositorSocketPath = join(runtimeDir, compositor.socket);
package/src/index.ts CHANGED
@@ -3,13 +3,13 @@ import createConfigPlugin from "@gtkx/config/vite-plugin";
3
3
  import { existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
- import { type HeadlessOptions, STATIC_HEADLESS_ENV } from "./headless-display.js";
6
+ import { type HeadlessOptions, STATIC_HEADLESS_ENV } from "./headless-display.ts";
7
7
 
8
8
  /**
9
- * Options accepted by the {@link gtkx} Vitest plugin. Every headless display
9
+ * Options accepted by the GTKX Vitest plugin. Every headless display
10
10
  * setting is optional and falls back to a built-in default when omitted.
11
11
  */
12
- type GtkxPluginOptions = Partial<HeadlessOptions>;
12
+ type PluginOptions = Partial<HeadlessOptions>;
13
13
 
14
14
  const GTKX_INLINE_DEPS: RegExp[] = [/@gtkx\/(?!native)/, /[/\\]\.gtkx[/\\]/];
15
15
 
@@ -20,7 +20,7 @@ const workerPreloadUrl = (): URL => {
20
20
  return pathToFileURL(path);
21
21
  };
22
22
 
23
- const headlessPreloadSpecifier = (options: GtkxPluginOptions): string => {
23
+ const headlessPreloadSpecifier = (options: PluginOptions): string => {
24
24
  const url = workerPreloadUrl();
25
25
 
26
26
  for (const [key, value] of Object.entries(options)) {
@@ -30,12 +30,6 @@ const headlessPreloadSpecifier = (options: GtkxPluginOptions): string => {
30
30
  return url.href;
31
31
  };
32
32
 
33
- const workerSetupPath = (): string => {
34
- const sibling = join(import.meta.dirname, "worker-setup.js");
35
-
36
- return existsSync(sibling) ? sibling : join(import.meta.dirname, "..", "dist", "worker-setup.js");
37
- };
38
-
39
33
  /**
40
34
  * Vitest plugin that runs each test worker against its own isolated headless
41
35
  * Wayland display. It configures the forks pool, injects the worker preload and
@@ -44,7 +38,7 @@ const workerSetupPath = (): string => {
44
38
  * @param options Headless display settings (size, compositor) forwarded to each worker.
45
39
  * @returns A Vitest config plugin.
46
40
  */
47
- const gtkx = (options: GtkxPluginOptions = {}): Plugin =>
41
+ const gtkx = (options: PluginOptions = {}): Plugin =>
48
42
  createConfigPlugin({
49
43
  name: "gtkx:vitest",
50
44
  config() {
@@ -52,7 +46,6 @@ const gtkx = (options: GtkxPluginOptions = {}): Plugin =>
52
46
  test: {
53
47
  globals: true,
54
48
  execArgv: ["--import", headlessPreloadSpecifier(options)],
55
- setupFiles: [workerSetupPath()],
56
49
  testTimeout: 30_000,
57
50
  hookTimeout: 30_000,
58
51
  pool: "forks",
@@ -68,5 +61,5 @@ const gtkx = (options: GtkxPluginOptions = {}): Plugin =>
68
61
  });
69
62
 
70
63
  export default gtkx;
71
- export { type CompositorId, type HeadlessOptions } from "./headless-display.js";
72
- export { type GtkxPluginOptions };
64
+ export { type CompositorId, type HeadlessOptions } from "./headless-display.ts";
65
+ export { type PluginOptions };
@@ -1,8 +1,8 @@
1
- import { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from "./headless-display.js";
2
- import { setHeadlessTeardown } from "./headless-globals.js";
1
+ import { installGracefulShutdown } from "@gtkx/utils";
2
+ import { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay } from "./headless-display.ts";
3
3
 
4
4
  const options = readHeadlessOptions(new URL(import.meta.url).searchParams);
5
5
  const teardown = await startHeadlessDisplay(resolveHeadlessOptions(options));
6
6
 
7
7
  process.on("exit", teardown);
8
- setHeadlessTeardown(teardown);
8
+ installGracefulShutdown({ onSignal: teardown });
@@ -1,10 +0,0 @@
1
- declare global {
2
- var gtkxHeadlessTeardown: (() => void) | undefined;
3
- var gtkxHeadlessShutdownInstalled: boolean | undefined;
4
- }
5
- declare const setHeadlessTeardown: (teardown: (() => void) | undefined) => void;
6
- declare const headlessTeardown: () => (() => void) | undefined;
7
- declare const setHeadlessShutdownInstalled: (installed: boolean | undefined) => void;
8
- declare const isHeadlessShutdownInstalled: () => boolean;
9
- export { setHeadlessTeardown, headlessTeardown, setHeadlessShutdownInstalled, isHeadlessShutdownInstalled };
10
- //# sourceMappingURL=headless-globals.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"headless-globals.d.ts","sourceRoot":"","sources":["../src/headless-globals.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACX,IAAI,oBAAoB,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACnD,IAAI,6BAA6B,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1D;AAMD,QAAA,MAAM,mBAAmB,GAAI,UAAU,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,KAAG,IAEjE,CAAC;AAEF,QAAA,MAAM,gBAAgB,QAAO,CAAC,MAAM,IAAI,CAAC,GAAG,SAA4C,CAAC;AAEzF,QAAA,MAAM,4BAA4B,GAAI,WAAW,OAAO,GAAG,SAAS,KAAG,IAEtE,CAAC;AAEF,QAAA,MAAM,2BAA2B,QAAO,OAA4D,CAAC;AAErG,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,CAAC"}
@@ -1,13 +0,0 @@
1
- const defineGlobal = (key, value) => {
2
- Object.defineProperty(globalThis, key, { value, configurable: true, writable: true });
3
- };
4
- const setHeadlessTeardown = (teardown) => {
5
- defineGlobal("gtkxHeadlessTeardown", teardown);
6
- };
7
- const headlessTeardown = () => globalThis.gtkxHeadlessTeardown;
8
- const setHeadlessShutdownInstalled = (installed) => {
9
- defineGlobal("gtkxHeadlessShutdownInstalled", installed);
10
- };
11
- const isHeadlessShutdownInstalled = () => globalThis.gtkxHeadlessShutdownInstalled === true;
12
- export { setHeadlessTeardown, headlessTeardown, setHeadlessShutdownInstalled, isHeadlessShutdownInstalled };
13
- //# sourceMappingURL=headless-globals.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"headless-globals.js","sourceRoot":"","sources":["../src/headless-globals.ts"],"names":[],"mappings":"AAKA,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,KAAc,EAAQ,EAAE;IACvD,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1F,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,QAAkC,EAAQ,EAAE;IACrE,YAAY,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,GAA6B,EAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC;AAEzF,MAAM,4BAA4B,GAAG,CAAC,SAA8B,EAAQ,EAAE;IAC1E,YAAY,CAAC,+BAA+B,EAAE,SAAS,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEF,MAAM,2BAA2B,GAAG,GAAY,EAAE,CAAC,UAAU,CAAC,6BAA6B,KAAK,IAAI,CAAC;AAErG,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,CAAC","sourcesContent":["declare global {\n var gtkxHeadlessTeardown: (() => void) | undefined;\n var gtkxHeadlessShutdownInstalled: boolean | undefined;\n}\n\nconst defineGlobal = (key: string, value: unknown): void => {\n Object.defineProperty(globalThis, key, { value, configurable: true, writable: true });\n};\n\nconst setHeadlessTeardown = (teardown: (() => void) | undefined): void => {\n defineGlobal(\"gtkxHeadlessTeardown\", teardown);\n};\n\nconst headlessTeardown = (): (() => void) | undefined => globalThis.gtkxHeadlessTeardown;\n\nconst setHeadlessShutdownInstalled = (installed: boolean | undefined): void => {\n defineGlobal(\"gtkxHeadlessShutdownInstalled\", installed);\n};\n\nconst isHeadlessShutdownInstalled = (): boolean => globalThis.gtkxHeadlessShutdownInstalled === true;\n\nexport { setHeadlessTeardown, headlessTeardown, setHeadlessShutdownInstalled, isHeadlessShutdownInstalled };\n"]}
@@ -1,3 +0,0 @@
1
- declare const installHeadlessShutdown: () => void;
2
- export { installHeadlessShutdown };
3
- //# sourceMappingURL=install-headless-shutdown.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"install-headless-shutdown.d.ts","sourceRoot":"","sources":["../src/install-headless-shutdown.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,uBAAuB,QAAO,IASnC,CAAC;AAEF,OAAO,EAAE,uBAAuB,EAAE,CAAC"}
@@ -1,12 +0,0 @@
1
- import { installGracefulShutdown } from "@gtkx/utils";
2
- import { headlessTeardown, isHeadlessShutdownInstalled, setHeadlessShutdownInstalled } from "./headless-globals.js";
3
- const installHeadlessShutdown = () => {
4
- const teardown = headlessTeardown();
5
- if (teardown === undefined || isHeadlessShutdownInstalled()) {
6
- return;
7
- }
8
- setHeadlessShutdownInstalled(true);
9
- installGracefulShutdown({ onSignal: teardown });
10
- };
11
- export { installHeadlessShutdown };
12
- //# sourceMappingURL=install-headless-shutdown.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"install-headless-shutdown.js","sourceRoot":"","sources":["../src/install-headless-shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAEpH,MAAM,uBAAuB,GAAG,GAAS,EAAE;IACvC,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IAEpC,IAAI,QAAQ,KAAK,SAAS,IAAI,2BAA2B,EAAE,EAAE,CAAC;QAC1D,OAAO;IACX,CAAC;IAED,4BAA4B,CAAC,IAAI,CAAC,CAAC;IACnC,uBAAuB,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;AACpD,CAAC,CAAC;AAEF,OAAO,EAAE,uBAAuB,EAAE,CAAC","sourcesContent":["import { installGracefulShutdown } from \"@gtkx/utils\";\nimport { headlessTeardown, isHeadlessShutdownInstalled, setHeadlessShutdownInstalled } from \"./headless-globals.js\";\n\nconst installHeadlessShutdown = (): void => {\n const teardown = headlessTeardown();\n\n if (teardown === undefined || isHeadlessShutdownInstalled()) {\n return;\n }\n\n setHeadlessShutdownInstalled(true);\n installGracefulShutdown({ onSignal: teardown });\n};\n\nexport { installHeadlessShutdown };\n"]}
@@ -1,3 +0,0 @@
1
- declare const resolveExecutable: (command: string) => string;
2
- export { resolveExecutable };
3
- //# sourceMappingURL=resolve-executable.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resolve-executable.d.ts","sourceRoot":"","sources":["../src/resolve-executable.ts"],"names":[],"mappings":"AAaA,QAAA,MAAM,iBAAiB,GAAI,SAAS,MAAM,KAAG,MAgB5C,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
@@ -1,26 +0,0 @@
1
- import { accessSync, constants } from "node:fs";
2
- import { delimiter, isAbsolute, join } from "node:path";
3
- const isExecutable = (path) => {
4
- try {
5
- accessSync(path, constants.X_OK);
6
- return true;
7
- }
8
- catch {
9
- return false;
10
- }
11
- };
12
- const resolveExecutable = (command) => {
13
- if (isAbsolute(command)) {
14
- return command;
15
- }
16
- const searchPaths = (process.env.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0);
17
- for (const directory of searchPaths) {
18
- const candidate = join(directory, command);
19
- if (isExecutable(candidate)) {
20
- return candidate;
21
- }
22
- }
23
- throw new Error(`Cannot find the "${command}" executable on PATH`);
24
- };
25
- export { resolveExecutable };
26
- //# sourceMappingURL=resolve-executable.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resolve-executable.js","sourceRoot":"","sources":["../src/resolve-executable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAExD,MAAM,YAAY,GAAG,CAAC,IAAY,EAAW,EAAE;IAC3C,IAAI,CAAC;QACD,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAEjC,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAU,EAAE;IAClD,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAElG,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAE3C,IAAI,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACrB,CAAC;IACL,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,sBAAsB,CAAC,CAAC;AACvE,CAAC,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["import { accessSync, constants } from \"node:fs\";\nimport { delimiter, isAbsolute, join } from \"node:path\";\n\nconst isExecutable = (path: string): boolean => {\n try {\n accessSync(path, constants.X_OK);\n\n return true;\n } catch {\n return false;\n }\n};\n\nconst resolveExecutable = (command: string): string => {\n if (isAbsolute(command)) {\n return command;\n }\n\n const searchPaths = (process.env.PATH ?? \"\").split(delimiter).filter((entry) => entry.length > 0);\n\n for (const directory of searchPaths) {\n const candidate = join(directory, command);\n\n if (isExecutable(candidate)) {\n return candidate;\n }\n }\n\n throw new Error(`Cannot find the \"${command}\" executable on PATH`);\n};\n\nexport { resolveExecutable };\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=worker-setup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worker-setup.d.ts","sourceRoot":"","sources":["../src/worker-setup.ts"],"names":[],"mappings":""}
@@ -1,3 +0,0 @@
1
- import { installHeadlessShutdown } from "./install-headless-shutdown.js";
2
- installHeadlessShutdown();
3
- //# sourceMappingURL=worker-setup.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worker-setup.js","sourceRoot":"","sources":["../src/worker-setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,uBAAuB,EAAE,CAAC","sourcesContent":["import { installHeadlessShutdown } from \"./install-headless-shutdown.js\";\n\ninstallHeadlessShutdown();\n"]}
@@ -1,22 +0,0 @@
1
- declare global {
2
- var gtkxHeadlessTeardown: (() => void) | undefined;
3
- var gtkxHeadlessShutdownInstalled: boolean | undefined;
4
- }
5
-
6
- const defineGlobal = (key: string, value: unknown): void => {
7
- Object.defineProperty(globalThis, key, { value, configurable: true, writable: true });
8
- };
9
-
10
- const setHeadlessTeardown = (teardown: (() => void) | undefined): void => {
11
- defineGlobal("gtkxHeadlessTeardown", teardown);
12
- };
13
-
14
- const headlessTeardown = (): (() => void) | undefined => globalThis.gtkxHeadlessTeardown;
15
-
16
- const setHeadlessShutdownInstalled = (installed: boolean | undefined): void => {
17
- defineGlobal("gtkxHeadlessShutdownInstalled", installed);
18
- };
19
-
20
- const isHeadlessShutdownInstalled = (): boolean => globalThis.gtkxHeadlessShutdownInstalled === true;
21
-
22
- export { setHeadlessTeardown, headlessTeardown, setHeadlessShutdownInstalled, isHeadlessShutdownInstalled };
@@ -1,15 +0,0 @@
1
- import { installGracefulShutdown } from "@gtkx/utils";
2
- import { headlessTeardown, isHeadlessShutdownInstalled, setHeadlessShutdownInstalled } from "./headless-globals.js";
3
-
4
- const installHeadlessShutdown = (): void => {
5
- const teardown = headlessTeardown();
6
-
7
- if (teardown === undefined || isHeadlessShutdownInstalled()) {
8
- return;
9
- }
10
-
11
- setHeadlessShutdownInstalled(true);
12
- installGracefulShutdown({ onSignal: teardown });
13
- };
14
-
15
- export { installHeadlessShutdown };
@@ -1,32 +0,0 @@
1
- import { accessSync, constants } from "node:fs";
2
- import { delimiter, isAbsolute, join } from "node:path";
3
-
4
- const isExecutable = (path: string): boolean => {
5
- try {
6
- accessSync(path, constants.X_OK);
7
-
8
- return true;
9
- } catch {
10
- return false;
11
- }
12
- };
13
-
14
- const resolveExecutable = (command: string): string => {
15
- if (isAbsolute(command)) {
16
- return command;
17
- }
18
-
19
- const searchPaths = (process.env.PATH ?? "").split(delimiter).filter((entry) => entry.length > 0);
20
-
21
- for (const directory of searchPaths) {
22
- const candidate = join(directory, command);
23
-
24
- if (isExecutable(candidate)) {
25
- return candidate;
26
- }
27
- }
28
-
29
- throw new Error(`Cannot find the "${command}" executable on PATH`);
30
- };
31
-
32
- export { resolveExecutable };
@@ -1,3 +0,0 @@
1
- import { installHeadlessShutdown } from "./install-headless-shutdown.js";
2
-
3
- installHeadlessShutdown();