@gtkx/utils 1.0.0-rc.4 → 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.
Files changed (44) hide show
  1. package/README.md +5 -5
  2. package/dist/class/any-class.d.ts +2 -0
  3. package/dist/class/any-class.d.ts.map +1 -1
  4. package/dist/class/any-class.js.map +1 -1
  5. package/dist/map/get-or-insert.d.ts +1 -1
  6. package/dist/map/get-or-insert.d.ts.map +1 -1
  7. package/dist/map/get-or-insert.js.map +1 -1
  8. package/dist/process/index.d.ts +2 -1
  9. package/dist/process/index.d.ts.map +1 -1
  10. package/dist/process/index.js +2 -1
  11. package/dist/process/index.js.map +1 -1
  12. package/dist/process/install-graceful-shutdown.d.ts +1 -1
  13. package/dist/process/install-graceful-shutdown.d.ts.map +1 -1
  14. package/dist/process/install-graceful-shutdown.js.map +1 -1
  15. package/dist/process/kill-marked-processes.d.ts +4 -0
  16. package/dist/process/kill-marked-processes.d.ts.map +1 -0
  17. package/dist/process/kill-marked-processes.js +38 -0
  18. package/dist/process/kill-marked-processes.js.map +1 -0
  19. package/dist/process/process-guard.d.ts +2 -0
  20. package/dist/process/process-guard.d.ts.map +1 -0
  21. package/dist/process/process-guard.js +16 -0
  22. package/dist/process/process-guard.js.map +1 -0
  23. package/dist/process/resolve-executable.d.ts +2 -1
  24. package/dist/process/resolve-executable.d.ts.map +1 -1
  25. package/dist/process/resolve-executable.js +6 -3
  26. package/dist/process/resolve-executable.js.map +1 -1
  27. package/dist/process/spawn-with-parent-death-signal.d.ts +8 -0
  28. package/dist/process/spawn-with-parent-death-signal.d.ts.map +1 -0
  29. package/dist/process/spawn-with-parent-death-signal.js +50 -0
  30. package/dist/process/spawn-with-parent-death-signal.js.map +1 -0
  31. package/dist/util/package-version.d.ts +1 -1
  32. package/dist/util/package-version.d.ts.map +1 -1
  33. package/dist/util/package-version.js +2 -2
  34. package/dist/util/package-version.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/class/any-class.ts +2 -0
  37. package/src/map/get-or-insert.ts +1 -1
  38. package/src/process/index.ts +2 -1
  39. package/src/process/install-graceful-shutdown.ts +1 -1
  40. package/src/process/kill-marked-processes.ts +44 -0
  41. package/src/process/process-guard.ts +20 -0
  42. package/src/process/resolve-executable.ts +7 -3
  43. package/src/process/spawn-with-parent-death-signal.ts +74 -0
  44. package/src/util/package-version.ts +2 -2
package/README.md CHANGED
@@ -108,7 +108,7 @@ React Native and similar frameworks hide the native toolkit so one API can run e
108
108
 
109
109
  ### Why Node.js, and why generated bindings
110
110
 
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 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.
112
112
 
113
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.
114
114
 
@@ -121,10 +121,10 @@ GTKX is Linux-only and needs Node.js 24 or later. See [Requirements](#requiremen
121
121
  Scaffold a new app with the `create-gtkx` initializer:
122
122
 
123
123
  ```sh
124
- npm create gtkx@rc
124
+ npm create gtkx
125
125
  ```
126
126
 
127
- 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`.
128
128
 
129
129
  Then run your new app:
130
130
 
@@ -145,7 +145,7 @@ The documentation at **[gtkx.dev](https://gtkx.dev)** includes a step-by-step tu
145
145
 
146
146
  GTKX is Linux-only. You need:
147
147
 
148
- - 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`
149
149
  - Node.js 24 or later
150
150
 
151
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.
@@ -161,7 +161,7 @@ Explore the [example apps](https://github.com/gtkx-org/gtkx/tree/main/examples):
161
161
 
162
162
  ## Status
163
163
 
164
- GTKX 1.0 is at the release candidate stage.
164
+ GTKX 1.0 is released.
165
165
 
166
166
  ## Contributing
167
167
 
@@ -1,4 +1,6 @@
1
+ /** Any class constructor, abstract ones included, whose instances are `T`. */
1
2
  type AnyClass<T extends object = object> = (abstract new (...args: never[]) => T) & {
3
+ /** Object the class's instances inherit from. */
2
4
  prototype: T;
3
5
  };
4
6
  export { type AnyClass };
@@ -1 +1 @@
1
- {"version":3,"file":"any-class.d.ts","sourceRoot":"","sources":["../../src/class/any-class.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,MAChD,GAAG,IAAI,EAAE,KAAK,EAAE,KACf,CAAC,CAAC,GAAG;IACN,SAAS,EAAE,CAAC,CAAC;CAChB,CAAC;AAEF,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"any-class.d.ts","sourceRoot":"","sources":["../../src/class/any-class.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,MAChD,GAAG,IAAI,EAAE,KAAK,EAAE,KACf,CAAC,CAAC,GAAG;IACN,iDAAiD;IACjD,SAAS,EAAE,CAAC,CAAC;CAChB,CAAC;AAEF,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"any-class.js","sourceRoot":"","sources":["../../src/class/any-class.ts"],"names":[],"mappings":"AAMA,OAAO,EAAiB,CAAC","sourcesContent":["type AnyClass<T extends object = object> = (abstract new (\n ...args: never[]\n) => T) & {\n prototype: T;\n};\n\nexport { type AnyClass };\n"]}
1
+ {"version":3,"file":"any-class.js","sourceRoot":"","sources":["../../src/class/any-class.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,CAAC","sourcesContent":["/** Any class constructor, abstract ones included, whose instances are `T`. */\ntype AnyClass<T extends object = object> = (abstract new (\n ...args: never[]\n) => T) & {\n /** Object the class's instances inherit from. */\n prototype: T;\n};\n\nexport { type AnyClass };\n"]}
@@ -4,5 +4,5 @@ type GetSet<K, V> = {
4
4
  has(key: K): boolean;
5
5
  };
6
6
  declare function getOrInsert<K, V>(map: GetSet<K, V>, key: K, factory: (key: K) => V): V;
7
- export { getOrInsert, type GetSet };
7
+ export { getOrInsert };
8
8
  //# sourceMappingURL=get-or-insert.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-or-insert.d.ts","sourceRoot":"","sources":["../../src/map/get-or-insert.ts"],"names":[],"mappings":"AAAA,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI;IAChB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;IAC/B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,iBAAS,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAS/E;AAED,OAAO,EAAE,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"get-or-insert.d.ts","sourceRoot":"","sources":["../../src/map/get-or-insert.ts"],"names":[],"mappings":"AAAA,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI;IAChB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAC3B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;IAC/B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,iBAAS,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAS/E;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-or-insert.js","sourceRoot":"","sources":["../../src/map/get-or-insert.ts"],"names":[],"mappings":"AAMA,SAAS,WAAW,CAAO,GAAiB,EAAE,GAAM,EAAE,OAAsB;IACxE,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACf,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,CAAM,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAEpB,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,OAAO,EAAE,WAAW,EAAe,CAAC","sourcesContent":["type GetSet<K, V> = {\n get(key: K): V | undefined;\n set(key: K, value: V): unknown;\n has(key: K): boolean;\n};\n\nfunction getOrInsert<K, V>(map: GetSet<K, V>, key: K, factory: (key: K) => V): V {\n if (map.has(key)) {\n return map.get(key) as V;\n }\n\n const value = factory(key);\n map.set(key, value);\n\n return value;\n}\n\nexport { getOrInsert, type GetSet };\n"]}
1
+ {"version":3,"file":"get-or-insert.js","sourceRoot":"","sources":["../../src/map/get-or-insert.ts"],"names":[],"mappings":"AAMA,SAAS,WAAW,CAAO,GAAiB,EAAE,GAAM,EAAE,OAAsB;IACxE,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACf,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,CAAM,CAAC;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAEpB,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC","sourcesContent":["type GetSet<K, V> = {\n get(key: K): V | undefined;\n set(key: K, value: V): unknown;\n has(key: K): boolean;\n};\n\nfunction getOrInsert<K, V>(map: GetSet<K, V>, key: K, factory: (key: K) => V): V {\n if (map.has(key)) {\n return map.get(key) as V;\n }\n\n const value = factory(key);\n map.set(key, value);\n\n return value;\n}\n\nexport { getOrInsert };\n"]}
@@ -1,4 +1,5 @@
1
1
  export { exitCodeForSignal } from "./exit-code-for-signal.ts";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.ts";
3
- export { resolveExecutable } from "./resolve-executable.ts";
3
+ export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.ts";
4
+ export { spawnWithParentDeathSignal } from "./spawn-with-parent-death-signal.ts";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export { exitCodeForSignal } from "./exit-code-for-signal.js";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.js";
3
- export { resolveExecutable } from "./resolve-executable.js";
3
+ export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.js";
4
+ export { spawnWithParentDeathSignal } from "./spawn-with-parent-death-signal.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC","sourcesContent":["export { exitCodeForSignal } from \"./exit-code-for-signal.ts\";\nexport { installGracefulShutdown } from \"./install-graceful-shutdown.ts\";\nexport { resolveExecutable } from \"./resolve-executable.ts\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC","sourcesContent":["export { exitCodeForSignal } from \"./exit-code-for-signal.ts\";\nexport { installGracefulShutdown } from \"./install-graceful-shutdown.ts\";\nexport { resolveExecutable, tryResolveExecutable } from \"./resolve-executable.ts\";\nexport { spawnWithParentDeathSignal } from \"./spawn-with-parent-death-signal.ts\";\n"]}
@@ -6,5 +6,5 @@ type GracefulShutdownOptions = {
6
6
  exitCode?: (signal: NodeJS.Signals, isGraceful: boolean) => number;
7
7
  };
8
8
  declare function installGracefulShutdown(options: GracefulShutdownOptions): void;
9
- export { installGracefulShutdown, type GracefulShutdownOptions };
9
+ export { installGracefulShutdown };
10
10
  //# sourceMappingURL=install-graceful-shutdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"install-graceful-shutdown.d.ts","sourceRoot":"","sources":["../../src/process/install-graceful-shutdown.ts"],"names":[],"mappings":"AAGA,KAAK,uBAAuB,GAAG;IAC3B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,KAAK,MAAM,CAAC;CACtE,CAAC;AA2FF,iBAAS,uBAAuB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAiBvE;AAED,OAAO,EAAE,uBAAuB,EAAE,KAAK,uBAAuB,EAAE,CAAC"}
1
+ {"version":3,"file":"install-graceful-shutdown.d.ts","sourceRoot":"","sources":["../../src/process/install-graceful-shutdown.ts"],"names":[],"mappings":"AAGA,KAAK,uBAAuB,GAAG;IAC3B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,KAAK,MAAM,CAAC;CACtE,CAAC;AA2FF,iBAAS,uBAAuB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAiBvE;AAED,OAAO,EAAE,uBAAuB,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"install-graceful-shutdown.js","sourceRoot":"","sources":["../../src/process/install-graceful-shutdown.ts"],"names":[],"mappings":"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","sourcesContent":["import { error } from \"../log/default-logger.ts\";\nimport { exitCodeForSignal } from \"./exit-code-for-signal.ts\";\n\ntype GracefulShutdownOptions = {\n onSignal: (signal: NodeJS.Signals) => void | Promise<void>;\n onForce?: () => void;\n forceKillAfterMs?: number;\n coalesceWindowMs?: number;\n exitCode?: (signal: NodeJS.Signals, isGraceful: boolean) => number;\n};\n\ntype ShutdownState = {\n options: GracefulShutdownOptions;\n forceKillMs: number;\n coalesceWindowMs: number;\n firstSignal: NodeJS.Signals | null;\n hasExited: boolean;\n isCoalescing: boolean;\n forceTimer: NodeJS.Timeout | null;\n coalesceTimer: NodeJS.Timeout | null;\n};\n\nconst HANDLED_SIGNALS = [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"] as const satisfies NodeJS.Signals[];\nconst DEFAULT_FORCE_KILL_TIMEOUT_MS = 5000;\nconst DEFAULT_COALESCE_WINDOW_MS = 500;\n\nconst clearTimers = (state: ShutdownState): void => {\n if (state.forceTimer) {\n clearTimeout(state.forceTimer);\n state.forceTimer = null;\n }\n\n if (state.coalesceTimer) {\n clearTimeout(state.coalesceTimer);\n state.coalesceTimer = null;\n }\n};\n\nconst finish = (state: ShutdownState, signal: NodeJS.Signals, isGraceful: boolean): void => {\n if (state.hasExited) {\n return;\n }\n\n state.hasExited = true;\n clearTimers(state);\n const { exitCode } = state.options;\n const defaultCode = isGraceful ? 0 : exitCodeForSignal(signal);\n process.exit(exitCode ? exitCode(signal, isGraceful) : defaultCode);\n};\n\nconst runShutdown = async (state: ShutdownState, signal: NodeJS.Signals): Promise<void> => {\n try {\n await state.options.onSignal(signal);\n finish(state, signal, true);\n } catch (error_) {\n error(\"graceful shutdown failed\", error_);\n finish(state, signal, false);\n }\n};\n\nconst beginShutdown = (state: ShutdownState, signal: NodeJS.Signals): void => {\n state.firstSignal = signal;\n\n if (state.coalesceWindowMs > 0) {\n state.isCoalescing = true;\n\n state.coalesceTimer = setTimeout(() => {\n state.isCoalescing = false;\n }, state.coalesceWindowMs);\n\n state.coalesceTimer.unref();\n }\n\n if (state.options.onForce && state.forceKillMs > 0) {\n state.forceTimer = setTimeout(() => {\n state.options.onForce?.();\n finish(state, signal, false);\n }, state.forceKillMs);\n\n state.forceTimer.unref();\n }\n\n void runShutdown(state, signal);\n};\n\nconst handle = (state: ShutdownState, signal: NodeJS.Signals): void => {\n if (state.firstSignal === null) {\n beginShutdown(state, signal);\n\n return;\n }\n\n if (state.isCoalescing) {\n return;\n }\n\n state.options.onForce?.();\n finish(state, signal, false);\n};\n\nfunction installGracefulShutdown(options: GracefulShutdownOptions): void {\n const state: ShutdownState = {\n options,\n forceKillMs: options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_TIMEOUT_MS,\n coalesceWindowMs: options.coalesceWindowMs ?? DEFAULT_COALESCE_WINDOW_MS,\n firstSignal: null,\n hasExited: false,\n isCoalescing: false,\n forceTimer: null,\n coalesceTimer: null,\n };\n\n for (const sig of HANDLED_SIGNALS) {\n process.on(sig, () => {\n handle(state, sig);\n });\n }\n}\n\nexport { installGracefulShutdown, type GracefulShutdownOptions };\n"]}
1
+ {"version":3,"file":"install-graceful-shutdown.js","sourceRoot":"","sources":["../../src/process/install-graceful-shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAqB9D,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAqC,CAAC;AAC5F,MAAM,6BAA6B,GAAG,IAAI,CAAC;AAC3C,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAEvC,MAAM,WAAW,GAAG,CAAC,KAAoB,EAAQ,EAAE;IAC/C,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/B,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACtB,YAAY,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAClC,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;IAC/B,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAE,MAAsB,EAAE,UAAmB,EAAQ,EAAE;IACvF,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAClB,OAAO;IACX,CAAC;IAED,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;IACvB,WAAW,CAAC,KAAK,CAAC,CAAC;IACnB,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC;IACnC,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC/D,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;AACxE,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,KAAK,EAAE,KAAoB,EAAE,MAAsB,EAAiB,EAAE;IACtF,IAAI,CAAC;QACD,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QACd,KAAK,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,KAAoB,EAAE,MAAsB,EAAQ,EAAE;IACzE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC;IAE3B,IAAI,KAAK,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;QAE1B,KAAK,CAAC,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;YAClC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC;QAC/B,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAE3B,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;QACjD,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAC/B,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YAC1B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;QAEtB,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,KAAoB,EAAE,MAAsB,EAAQ,EAAE;IAClE,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QAC7B,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAE7B,OAAO;IACX,CAAC;IAED,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACrB,OAAO;IACX,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AACjC,CAAC,CAAC;AAEF,SAAS,uBAAuB,CAAC,OAAgC;IAC7D,MAAM,KAAK,GAAkB;QACzB,OAAO;QACP,WAAW,EAAE,OAAO,CAAC,gBAAgB,IAAI,6BAA6B;QACtE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,0BAA0B;QACxE,WAAW,EAAE,IAAI;QACjB,SAAS,EAAE,KAAK;QAChB,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI;KACtB,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE;YACjB,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;IACP,CAAC;AACL,CAAC;AAED,OAAO,EAAE,uBAAuB,EAAE,CAAC","sourcesContent":["import { error } from \"../log/default-logger.ts\";\nimport { exitCodeForSignal } from \"./exit-code-for-signal.ts\";\n\ntype GracefulShutdownOptions = {\n onSignal: (signal: NodeJS.Signals) => void | Promise<void>;\n onForce?: () => void;\n forceKillAfterMs?: number;\n coalesceWindowMs?: number;\n exitCode?: (signal: NodeJS.Signals, isGraceful: boolean) => number;\n};\n\ntype ShutdownState = {\n options: GracefulShutdownOptions;\n forceKillMs: number;\n coalesceWindowMs: number;\n firstSignal: NodeJS.Signals | null;\n hasExited: boolean;\n isCoalescing: boolean;\n forceTimer: NodeJS.Timeout | null;\n coalesceTimer: NodeJS.Timeout | null;\n};\n\nconst HANDLED_SIGNALS = [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"] as const satisfies NodeJS.Signals[];\nconst DEFAULT_FORCE_KILL_TIMEOUT_MS = 5000;\nconst DEFAULT_COALESCE_WINDOW_MS = 500;\n\nconst clearTimers = (state: ShutdownState): void => {\n if (state.forceTimer) {\n clearTimeout(state.forceTimer);\n state.forceTimer = null;\n }\n\n if (state.coalesceTimer) {\n clearTimeout(state.coalesceTimer);\n state.coalesceTimer = null;\n }\n};\n\nconst finish = (state: ShutdownState, signal: NodeJS.Signals, isGraceful: boolean): void => {\n if (state.hasExited) {\n return;\n }\n\n state.hasExited = true;\n clearTimers(state);\n const { exitCode } = state.options;\n const defaultCode = isGraceful ? 0 : exitCodeForSignal(signal);\n process.exit(exitCode ? exitCode(signal, isGraceful) : defaultCode);\n};\n\nconst runShutdown = async (state: ShutdownState, signal: NodeJS.Signals): Promise<void> => {\n try {\n await state.options.onSignal(signal);\n finish(state, signal, true);\n } catch (error_) {\n error(\"graceful shutdown failed\", error_);\n finish(state, signal, false);\n }\n};\n\nconst beginShutdown = (state: ShutdownState, signal: NodeJS.Signals): void => {\n state.firstSignal = signal;\n\n if (state.coalesceWindowMs > 0) {\n state.isCoalescing = true;\n\n state.coalesceTimer = setTimeout(() => {\n state.isCoalescing = false;\n }, state.coalesceWindowMs);\n\n state.coalesceTimer.unref();\n }\n\n if (state.options.onForce && state.forceKillMs > 0) {\n state.forceTimer = setTimeout(() => {\n state.options.onForce?.();\n finish(state, signal, false);\n }, state.forceKillMs);\n\n state.forceTimer.unref();\n }\n\n void runShutdown(state, signal);\n};\n\nconst handle = (state: ShutdownState, signal: NodeJS.Signals): void => {\n if (state.firstSignal === null) {\n beginShutdown(state, signal);\n\n return;\n }\n\n if (state.isCoalescing) {\n return;\n }\n\n state.options.onForce?.();\n finish(state, signal, false);\n};\n\nfunction installGracefulShutdown(options: GracefulShutdownOptions): void {\n const state: ShutdownState = {\n options,\n forceKillMs: options.forceKillAfterMs ?? DEFAULT_FORCE_KILL_TIMEOUT_MS,\n coalesceWindowMs: options.coalesceWindowMs ?? DEFAULT_COALESCE_WINDOW_MS,\n firstSignal: null,\n hasExited: false,\n isCoalescing: false,\n forceTimer: null,\n coalesceTimer: null,\n };\n\n for (const sig of HANDLED_SIGNALS) {\n process.on(sig, () => {\n handle(state, sig);\n });\n }\n}\n\nexport { installGracefulShutdown };\n"]}
@@ -0,0 +1,4 @@
1
+ declare const PROCESS_MARKER = "GTKX_PROCESS_GUARD";
2
+ declare function killMarkedProcesses(prefix: string): void;
3
+ export { PROCESS_MARKER, killMarkedProcesses };
4
+ //# sourceMappingURL=kill-marked-processes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kill-marked-processes.d.ts","sourceRoot":"","sources":["../../src/process/kill-marked-processes.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,cAAc,uBAAuB,CAAC;AA2B5C,iBAAS,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAYjD;AAED,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ const PROCESS_MARKER = "GTKX_PROCESS_GUARD";
3
+ const MAX_KILL_PASSES = 8;
4
+ const isMarked = (pid, prefix) => {
5
+ try {
6
+ return readFileSync(`/proc/${String(pid)}/environ`, "utf8")
7
+ .split("\0")
8
+ .some((assignment) => assignment.startsWith(prefix));
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ };
14
+ const markedPids = (prefix) => readdirSync("/proc")
15
+ .map(Number)
16
+ .filter((pid) => Number.isSafeInteger(pid) && pid > 1 && pid !== process.pid)
17
+ .filter((pid) => isMarked(pid, prefix));
18
+ const killPid = (pid) => {
19
+ try {
20
+ process.kill(pid, "SIGKILL");
21
+ }
22
+ catch {
23
+ return;
24
+ }
25
+ };
26
+ function killMarkedProcesses(prefix) {
27
+ for (let pass = 0; pass < MAX_KILL_PASSES; pass += 1) {
28
+ const pids = markedPids(prefix);
29
+ if (pids.length === 0) {
30
+ return;
31
+ }
32
+ for (const pid of pids) {
33
+ killPid(pid);
34
+ }
35
+ }
36
+ }
37
+ export { PROCESS_MARKER, killMarkedProcesses };
38
+ //# sourceMappingURL=kill-marked-processes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kill-marked-processes.js","sourceRoot":"","sources":["../../src/process/kill-marked-processes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,cAAc,GAAG,oBAAoB,CAAC;AAC5C,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,MAAc,EAAW,EAAE;IACtD,IAAI,CAAC;QACD,OAAO,YAAY,CAAC,SAAS,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC;aACtD,KAAK,CAAC,IAAI,CAAC;aACX,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,MAAc,EAAY,EAAE,CAC5C,WAAW,CAAC,OAAO,CAAC;KACf,GAAG,CAAC,MAAM,CAAC;KACX,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC;KAC5E,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;AAEhD,MAAM,OAAO,GAAG,CAAC,GAAW,EAAQ,EAAE;IAClC,IAAI,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO;IACX,CAAC;AACL,CAAC,CAAC;AAEF,SAAS,mBAAmB,CAAC,MAAc;IACvC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,eAAe,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAEhC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpB,OAAO;QACX,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC;IACL,CAAC;AACL,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAC","sourcesContent":["import { readdirSync, readFileSync } from \"node:fs\";\n\nconst PROCESS_MARKER = \"GTKX_PROCESS_GUARD\";\nconst MAX_KILL_PASSES = 8;\n\nconst isMarked = (pid: number, prefix: string): boolean => {\n try {\n return readFileSync(`/proc/${String(pid)}/environ`, \"utf8\")\n .split(\"\\0\")\n .some((assignment) => assignment.startsWith(prefix));\n } catch {\n return false;\n }\n};\n\nconst markedPids = (prefix: string): number[] =>\n readdirSync(\"/proc\")\n .map(Number)\n .filter((pid) => Number.isSafeInteger(pid) && pid > 1 && pid !== process.pid)\n .filter((pid) => isMarked(pid, prefix));\n\nconst killPid = (pid: number): void => {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n return;\n }\n};\n\nfunction killMarkedProcesses(prefix: string): void {\n for (let pass = 0; pass < MAX_KILL_PASSES; pass += 1) {\n const pids = markedPids(prefix);\n\n if (pids.length === 0) {\n return;\n }\n\n for (const pid of pids) {\n killPid(pid);\n }\n }\n}\n\nexport { PROCESS_MARKER, killMarkedProcesses };\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=process-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-guard.d.ts","sourceRoot":"","sources":["../../src/process/process-guard.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import { killMarkedProcesses } from "./kill-marked-processes.js";
2
+ const GUARD_PREFIX = process.argv[2] ?? "";
3
+ const WATCHED_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
4
+ const sweep = () => {
5
+ if (GUARD_PREFIX.length > 0) {
6
+ killMarkedProcesses(GUARD_PREFIX);
7
+ }
8
+ process.exit(0);
9
+ };
10
+ process.stdin.resume();
11
+ process.stdin.on("end", sweep);
12
+ process.stdin.on("error", sweep);
13
+ for (const signal of WATCHED_SIGNALS) {
14
+ process.on(signal, sweep);
15
+ }
16
+ //# sourceMappingURL=process-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-guard.js","sourceRoot":"","sources":["../../src/process/process-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3C,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAqC,CAAC;AAE5F,MAAM,KAAK,GAAG,GAAS,EAAE;IACrB,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC;AAEF,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;AACvB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC/B,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAEjC,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["import { killMarkedProcesses } from \"./kill-marked-processes.ts\";\n\nconst GUARD_PREFIX = process.argv[2] ?? \"\";\nconst WATCHED_SIGNALS = [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"] as const satisfies NodeJS.Signals[];\n\nconst sweep = (): void => {\n if (GUARD_PREFIX.length > 0) {\n killMarkedProcesses(GUARD_PREFIX);\n }\n\n process.exit(0);\n};\n\nprocess.stdin.resume();\nprocess.stdin.on(\"end\", sweep);\nprocess.stdin.on(\"error\", sweep);\n\nfor (const signal of WATCHED_SIGNALS) {\n process.on(signal, sweep);\n}\n"]}
@@ -1,3 +1,4 @@
1
+ declare function tryResolveExecutable(command: string): string | undefined;
1
2
  declare function resolveExecutable(command: string): string;
2
- export { resolveExecutable };
3
+ export { resolveExecutable, tryResolveExecutable };
3
4
  //# sourceMappingURL=resolve-executable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-executable.d.ts","sourceRoot":"","sources":["../../src/process/resolve-executable.ts"],"names":[],"mappings":"AA2BA,iBAAS,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBlD;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
1
+ {"version":3,"file":"resolve-executable.d.ts","sourceRoot":"","sources":["../../src/process/resolve-executable.ts"],"names":[],"mappings":"AA2BA,iBAAS,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUjE;AAED,iBAAS,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,CAAC"}
@@ -19,18 +19,21 @@ const findOnPath = (command) => {
19
19
  }
20
20
  return undefined;
21
21
  };
22
- function resolveExecutable(command) {
22
+ function tryResolveExecutable(command) {
23
23
  if (isAbsolute(command)) {
24
24
  return command;
25
25
  }
26
26
  if (command.includes("/")) {
27
27
  return resolve(command);
28
28
  }
29
- const found = findOnPath(command);
29
+ return findOnPath(command);
30
+ }
31
+ function resolveExecutable(command) {
32
+ const found = tryResolveExecutable(command);
30
33
  if (found === undefined) {
31
34
  throw new Error(`Cannot find the "${command}" executable on PATH`);
32
35
  }
33
36
  return found;
34
37
  }
35
- export { resolveExecutable };
38
+ export { resolveExecutable, tryResolveExecutable };
36
39
  //# sourceMappingURL=resolve-executable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-executable.js","sourceRoot":"","sources":["../../src/process/resolve-executable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEjE,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,UAAU,GAAG,CAAC,OAAe,EAAsB,EAAE;IACvD,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,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AAEF,SAAS,iBAAiB,CAAC,OAAe;IACtC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAElC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,sBAAsB,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["import { accessSync, constants } from \"node:fs\";\nimport { delimiter, isAbsolute, join, resolve } 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 findOnPath = (command: string): string | undefined => {\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 return undefined;\n};\n\nfunction resolveExecutable(command: string): string {\n if (isAbsolute(command)) {\n return command;\n }\n\n if (command.includes(\"/\")) {\n return resolve(command);\n }\n\n const found = findOnPath(command);\n\n if (found === undefined) {\n throw new Error(`Cannot find the \"${command}\" executable on PATH`);\n }\n\n return found;\n}\n\nexport { resolveExecutable };\n"]}
1
+ {"version":3,"file":"resolve-executable.js","sourceRoot":"","sources":["../../src/process/resolve-executable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEjE,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,UAAU,GAAG,CAAC,OAAe,EAAsB,EAAE;IACvD,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,OAAO,SAAS,CAAC;AACrB,CAAC,CAAC;AAEF,SAAS,oBAAoB,CAAC,OAAe;IACzC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAe;IACtC,MAAM,KAAK,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAE5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,sBAAsB,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,CAAC","sourcesContent":["import { accessSync, constants } from \"node:fs\";\nimport { delimiter, isAbsolute, join, resolve } 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 findOnPath = (command: string): string | undefined => {\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 return undefined;\n};\n\nfunction tryResolveExecutable(command: string): string | undefined {\n if (isAbsolute(command)) {\n return command;\n }\n\n if (command.includes(\"/\")) {\n return resolve(command);\n }\n\n return findOnPath(command);\n}\n\nfunction resolveExecutable(command: string): string {\n const found = tryResolveExecutable(command);\n\n if (found === undefined) {\n throw new Error(`Cannot find the \"${command}\" executable on PATH`);\n }\n\n return found;\n}\n\nexport { resolveExecutable, tryResolveExecutable };\n"]}
@@ -0,0 +1,8 @@
1
+ import { type ChildProcess, type StdioOptions } from "node:child_process";
2
+ type ParentDeathSpawnOptions = {
3
+ stdio?: StdioOptions;
4
+ env?: NodeJS.ProcessEnv;
5
+ };
6
+ declare function spawnWithParentDeathSignal(command: string, args: string[], options?: ParentDeathSpawnOptions): ChildProcess;
7
+ export { spawnWithParentDeathSignal };
8
+ //# sourceMappingURL=spawn-with-parent-death-signal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-with-parent-death-signal.d.ts","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQjF,KAAK,uBAAuB,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAC3B,CAAC;AAuCF,iBAAS,0BAA0B,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,uBAA4B,GACtC,YAAY,CAiBd;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { Socket } from "node:net";
4
+ import { dirname, extname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { killMarkedProcesses, PROCESS_MARKER } from "./kill-marked-processes.js";
7
+ import { resolveExecutable } from "./resolve-executable.js";
8
+ const MODULE_PATH = fileURLToPath(import.meta.url);
9
+ const GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);
10
+ const RUN_ID = randomBytes(8).toString("hex");
11
+ const RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;
12
+ const guard = {};
13
+ const releaseGuard = (child) => {
14
+ child.unref();
15
+ const { stdin } = child;
16
+ if (stdin instanceof Socket) {
17
+ stdin.unref();
18
+ }
19
+ };
20
+ const startGuard = () => {
21
+ if (guard.child) {
22
+ return;
23
+ }
24
+ const child = spawn(process.execPath, [GUARD_PATH, RUN_PREFIX], {
25
+ detached: true,
26
+ stdio: ["pipe", "ignore", "ignore"],
27
+ });
28
+ child.on("error", () => {
29
+ guard.child = undefined;
30
+ });
31
+ guard.child = child;
32
+ releaseGuard(child);
33
+ };
34
+ function spawnWithParentDeathSignal(command, args, options = {}) {
35
+ const executable = resolveExecutable(command);
36
+ const setpriv = resolveExecutable("setpriv");
37
+ const jobValue = `${RUN_ID}/${randomBytes(4).toString("hex")}`;
38
+ startGuard();
39
+ const child = spawn(setpriv, ["--pdeathsig", "SIGKILL", executable, ...args], {
40
+ detached: true,
41
+ stdio: options.stdio ?? "ignore",
42
+ env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
43
+ });
44
+ child.on("exit", () => {
45
+ killMarkedProcesses(`${PROCESS_MARKER}=${jobValue}`);
46
+ });
47
+ return child;
48
+ }
49
+ export { spawnWithParentDeathSignal };
50
+ //# sourceMappingURL=spawn-with-parent-death-signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-with-parent-death-signal.js","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn, type StdioOptions } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { Socket } from \"node:net\";\nimport { dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { killMarkedProcesses, PROCESS_MARKER } from \"./kill-marked-processes.ts\";\nimport { resolveExecutable } from \"./resolve-executable.ts\";\n\ntype ParentDeathSpawnOptions = {\n stdio?: StdioOptions;\n env?: NodeJS.ProcessEnv;\n};\n\ntype GuardState = {\n child?: ChildProcess | undefined;\n};\n\nconst MODULE_PATH = fileURLToPath(import.meta.url);\nconst GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);\nconst RUN_ID = randomBytes(8).toString(\"hex\");\nconst RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;\nconst guard: GuardState = {};\n\nconst releaseGuard = (child: ChildProcess): void => {\n child.unref();\n const { stdin } = child;\n\n if (stdin instanceof Socket) {\n stdin.unref();\n }\n};\n\nconst startGuard = (): void => {\n if (guard.child) {\n return;\n }\n\n const child = spawn(process.execPath, [GUARD_PATH, RUN_PREFIX], {\n detached: true,\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n\n child.on(\"error\", () => {\n guard.child = undefined;\n });\n\n guard.child = child;\n releaseGuard(child);\n};\n\nfunction spawnWithParentDeathSignal(\n command: string,\n args: string[],\n options: ParentDeathSpawnOptions = {},\n): ChildProcess {\n const executable = resolveExecutable(command);\n const setpriv = resolveExecutable(\"setpriv\");\n const jobValue = `${RUN_ID}/${randomBytes(4).toString(\"hex\")}`;\n startGuard();\n\n const child = spawn(setpriv, [\"--pdeathsig\", \"SIGKILL\", executable, ...args], {\n detached: true,\n stdio: options.stdio ?? \"ignore\",\n env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },\n });\n\n child.on(\"exit\", () => {\n killMarkedProcesses(`${PROCESS_MARKER}=${jobValue}`);\n });\n\n return child;\n}\n\nexport { spawnWithParentDeathSignal };\n"]}
@@ -1,3 +1,3 @@
1
- declare function packageVersion(importMetaUrl: string): string;
1
+ declare function packageVersion(importMetaUrl: string, specifier: string): string;
2
2
  export { packageVersion };
3
3
  //# sourceMappingURL=package-version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"package-version.d.ts","sourceRoot":"","sources":["../../src/util/package-version.ts"],"names":[],"mappings":"AAEA,iBAAS,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"package-version.d.ts","sourceRoot":"","sources":["../../src/util/package-version.ts"],"names":[],"mappings":"AAEA,iBAAS,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
- function packageVersion(importMetaUrl) {
3
- return createRequire(importMetaUrl)("../package.json").version;
2
+ function packageVersion(importMetaUrl, specifier) {
3
+ return createRequire(importMetaUrl)(specifier).version;
4
4
  }
5
5
  export { packageVersion };
6
6
  //# sourceMappingURL=package-version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"package-version.js","sourceRoot":"","sources":["../../src/util/package-version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,SAAS,cAAc,CAAC,aAAqB;IACzC,OAAQ,aAAa,CAAC,aAAa,CAAC,CAAC,iBAAiB,CAAyB,CAAC,OAAO,CAAC;AAC5F,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC","sourcesContent":["import { createRequire } from \"node:module\";\n\nfunction packageVersion(importMetaUrl: string): string {\n return (createRequire(importMetaUrl)(\"../package.json\") as { version: string }).version;\n}\n\nexport { packageVersion };\n"]}
1
+ {"version":3,"file":"package-version.js","sourceRoot":"","sources":["../../src/util/package-version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,SAAS,cAAc,CAAC,aAAqB,EAAE,SAAiB;IAC5D,OAAQ,aAAa,CAAC,aAAa,CAAC,CAAC,SAAS,CAAyB,CAAC,OAAO,CAAC;AACpF,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC","sourcesContent":["import { createRequire } from \"node:module\";\n\nfunction packageVersion(importMetaUrl: string, specifier: string): string {\n return (createRequire(importMetaUrl)(specifier) as { version: string }).version;\n}\n\nexport { packageVersion };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/utils",
3
- "version": "1.0.0-rc.4",
3
+ "version": "1.0.0",
4
4
  "description": "Cross-package utilities shared by the GTKX packages and toolchain: logging, error formatting, string and class helpers",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -1,6 +1,8 @@
1
+ /** Any class constructor, abstract ones included, whose instances are `T`. */
1
2
  type AnyClass<T extends object = object> = (abstract new (
2
3
  ...args: never[]
3
4
  ) => T) & {
5
+ /** Object the class's instances inherit from. */
4
6
  prototype: T;
5
7
  };
6
8
 
@@ -15,4 +15,4 @@ function getOrInsert<K, V>(map: GetSet<K, V>, key: K, factory: (key: K) => V): V
15
15
  return value;
16
16
  }
17
17
 
18
- export { getOrInsert, type GetSet };
18
+ export { getOrInsert };
@@ -1,3 +1,4 @@
1
1
  export { exitCodeForSignal } from "./exit-code-for-signal.ts";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.ts";
3
- export { resolveExecutable } from "./resolve-executable.ts";
3
+ export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.ts";
4
+ export { spawnWithParentDeathSignal } from "./spawn-with-parent-death-signal.ts";
@@ -117,4 +117,4 @@ function installGracefulShutdown(options: GracefulShutdownOptions): void {
117
117
  }
118
118
  }
119
119
 
120
- export { installGracefulShutdown, type GracefulShutdownOptions };
120
+ export { installGracefulShutdown };
@@ -0,0 +1,44 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+
3
+ const PROCESS_MARKER = "GTKX_PROCESS_GUARD";
4
+ const MAX_KILL_PASSES = 8;
5
+
6
+ const isMarked = (pid: number, prefix: string): boolean => {
7
+ try {
8
+ return readFileSync(`/proc/${String(pid)}/environ`, "utf8")
9
+ .split("\0")
10
+ .some((assignment) => assignment.startsWith(prefix));
11
+ } catch {
12
+ return false;
13
+ }
14
+ };
15
+
16
+ const markedPids = (prefix: string): number[] =>
17
+ readdirSync("/proc")
18
+ .map(Number)
19
+ .filter((pid) => Number.isSafeInteger(pid) && pid > 1 && pid !== process.pid)
20
+ .filter((pid) => isMarked(pid, prefix));
21
+
22
+ const killPid = (pid: number): void => {
23
+ try {
24
+ process.kill(pid, "SIGKILL");
25
+ } catch {
26
+ return;
27
+ }
28
+ };
29
+
30
+ function killMarkedProcesses(prefix: string): void {
31
+ for (let pass = 0; pass < MAX_KILL_PASSES; pass += 1) {
32
+ const pids = markedPids(prefix);
33
+
34
+ if (pids.length === 0) {
35
+ return;
36
+ }
37
+
38
+ for (const pid of pids) {
39
+ killPid(pid);
40
+ }
41
+ }
42
+ }
43
+
44
+ export { PROCESS_MARKER, killMarkedProcesses };
@@ -0,0 +1,20 @@
1
+ import { killMarkedProcesses } from "./kill-marked-processes.ts";
2
+
3
+ const GUARD_PREFIX = process.argv[2] ?? "";
4
+ const WATCHED_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"] as const satisfies NodeJS.Signals[];
5
+
6
+ const sweep = (): void => {
7
+ if (GUARD_PREFIX.length > 0) {
8
+ killMarkedProcesses(GUARD_PREFIX);
9
+ }
10
+
11
+ process.exit(0);
12
+ };
13
+
14
+ process.stdin.resume();
15
+ process.stdin.on("end", sweep);
16
+ process.stdin.on("error", sweep);
17
+
18
+ for (const signal of WATCHED_SIGNALS) {
19
+ process.on(signal, sweep);
20
+ }
@@ -25,7 +25,7 @@ const findOnPath = (command: string): string | undefined => {
25
25
  return undefined;
26
26
  };
27
27
 
28
- function resolveExecutable(command: string): string {
28
+ function tryResolveExecutable(command: string): string | undefined {
29
29
  if (isAbsolute(command)) {
30
30
  return command;
31
31
  }
@@ -34,7 +34,11 @@ function resolveExecutable(command: string): string {
34
34
  return resolve(command);
35
35
  }
36
36
 
37
- const found = findOnPath(command);
37
+ return findOnPath(command);
38
+ }
39
+
40
+ function resolveExecutable(command: string): string {
41
+ const found = tryResolveExecutable(command);
38
42
 
39
43
  if (found === undefined) {
40
44
  throw new Error(`Cannot find the "${command}" executable on PATH`);
@@ -43,4 +47,4 @@ function resolveExecutable(command: string): string {
43
47
  return found;
44
48
  }
45
49
 
46
- export { resolveExecutable };
50
+ export { resolveExecutable, tryResolveExecutable };
@@ -0,0 +1,74 @@
1
+ import { type ChildProcess, spawn, type StdioOptions } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { Socket } from "node:net";
4
+ import { dirname, extname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { killMarkedProcesses, PROCESS_MARKER } from "./kill-marked-processes.ts";
7
+ import { resolveExecutable } from "./resolve-executable.ts";
8
+
9
+ type ParentDeathSpawnOptions = {
10
+ stdio?: StdioOptions;
11
+ env?: NodeJS.ProcessEnv;
12
+ };
13
+
14
+ type GuardState = {
15
+ child?: ChildProcess | undefined;
16
+ };
17
+
18
+ const MODULE_PATH = fileURLToPath(import.meta.url);
19
+ const GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);
20
+ const RUN_ID = randomBytes(8).toString("hex");
21
+ const RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;
22
+ const guard: GuardState = {};
23
+
24
+ const releaseGuard = (child: ChildProcess): void => {
25
+ child.unref();
26
+ const { stdin } = child;
27
+
28
+ if (stdin instanceof Socket) {
29
+ stdin.unref();
30
+ }
31
+ };
32
+
33
+ const startGuard = (): void => {
34
+ if (guard.child) {
35
+ return;
36
+ }
37
+
38
+ const child = spawn(process.execPath, [GUARD_PATH, RUN_PREFIX], {
39
+ detached: true,
40
+ stdio: ["pipe", "ignore", "ignore"],
41
+ });
42
+
43
+ child.on("error", () => {
44
+ guard.child = undefined;
45
+ });
46
+
47
+ guard.child = child;
48
+ releaseGuard(child);
49
+ };
50
+
51
+ function spawnWithParentDeathSignal(
52
+ command: string,
53
+ args: string[],
54
+ options: ParentDeathSpawnOptions = {},
55
+ ): ChildProcess {
56
+ const executable = resolveExecutable(command);
57
+ const setpriv = resolveExecutable("setpriv");
58
+ const jobValue = `${RUN_ID}/${randomBytes(4).toString("hex")}`;
59
+ startGuard();
60
+
61
+ const child = spawn(setpriv, ["--pdeathsig", "SIGKILL", executable, ...args], {
62
+ detached: true,
63
+ stdio: options.stdio ?? "ignore",
64
+ env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
65
+ });
66
+
67
+ child.on("exit", () => {
68
+ killMarkedProcesses(`${PROCESS_MARKER}=${jobValue}`);
69
+ });
70
+
71
+ return child;
72
+ }
73
+
74
+ export { spawnWithParentDeathSignal };
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
 
3
- function packageVersion(importMetaUrl: string): string {
4
- return (createRequire(importMetaUrl)("../package.json") as { version: string }).version;
3
+ function packageVersion(importMetaUrl: string, specifier: string): string {
4
+ return (createRequire(importMetaUrl)(specifier) as { version: string }).version;
5
5
  }
6
6
 
7
7
  export { packageVersion };