@gtkx/utils 1.6.0 → 2.0.0-beta.2

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 (50) hide show
  1. package/README.md +5 -5
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/path.d.ts +5 -0
  7. package/dist/path.d.ts.map +1 -0
  8. package/dist/path.js +10 -0
  9. package/dist/path.js.map +1 -0
  10. package/dist/process/index.d.ts +1 -0
  11. package/dist/process/index.d.ts.map +1 -1
  12. package/dist/process/index.js +1 -0
  13. package/dist/process/index.js.map +1 -1
  14. package/dist/process/kill-marked-processes.d.ts +3 -2
  15. package/dist/process/kill-marked-processes.d.ts.map +1 -1
  16. package/dist/process/kill-marked-processes.js +44 -13
  17. package/dist/process/kill-marked-processes.js.map +1 -1
  18. package/dist/process/kill-process-group.d.ts +16 -0
  19. package/dist/process/kill-process-group.d.ts.map +1 -0
  20. package/dist/process/kill-process-group.js +72 -0
  21. package/dist/process/kill-process-group.js.map +1 -0
  22. package/dist/process/process-guard.js +77 -2
  23. package/dist/process/process-guard.js.map +1 -1
  24. package/dist/process/resolve-executable.d.ts +2 -2
  25. package/dist/process/resolve-executable.d.ts.map +1 -1
  26. package/dist/process/resolve-executable.js +3 -37
  27. package/dist/process/resolve-executable.js.map +1 -1
  28. package/dist/process/spawn-with-parent-death-signal.d.ts +1 -0
  29. package/dist/process/spawn-with-parent-death-signal.d.ts.map +1 -1
  30. package/dist/process/spawn-with-parent-death-signal.js +66 -3
  31. package/dist/process/spawn-with-parent-death-signal.js.map +1 -1
  32. package/package.json +9 -3
  33. package/src/index.ts +1 -1
  34. package/src/path.ts +17 -0
  35. package/src/process/index.ts +5 -0
  36. package/src/process/kill-marked-processes.ts +62 -13
  37. package/src/process/kill-process-group.ts +104 -0
  38. package/src/process/process-guard.ts +106 -2
  39. package/src/process/resolve-executable.ts +4 -46
  40. package/src/process/spawn-with-parent-death-signal.ts +106 -3
  41. package/dist/map/get-or-insert.d.ts +0 -8
  42. package/dist/map/get-or-insert.d.ts.map +0 -1
  43. package/dist/map/get-or-insert.js +0 -10
  44. package/dist/map/get-or-insert.js.map +0 -1
  45. package/dist/map/index.d.ts +0 -2
  46. package/dist/map/index.d.ts.map +0 -1
  47. package/dist/map/index.js +0 -2
  48. package/dist/map/index.js.map +0 -1
  49. package/src/map/get-or-insert.ts +0 -18
  50. package/src/map/index.ts +0 -1
@@ -4,22 +4,49 @@ import { Socket } from "node:net";
4
4
  import { dirname, extname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { killMarkedProcesses, PROCESS_MARKER } from "./kill-marked-processes.ts";
7
+ import {
8
+ type CleanupDirectoryIdentity,
9
+ cleanupDirectoryIdentity,
10
+ killProcessGroup,
11
+ type ProcessGroupIdentity,
12
+ processGroupIdentity,
13
+ removeCleanupDirectory,
14
+ } from "./kill-process-group.ts";
7
15
  import { resolveExecutable } from "./resolve-executable.ts";
8
16
 
9
17
  type ParentDeathSpawnOptions = {
10
18
  stdio?: StdioOptions;
11
19
  env?: NodeJS.ProcessEnv;
20
+ cleanupDirectories?: string[];
21
+ };
22
+
23
+ type GuardJob = {
24
+ marker: string;
25
+ processGroup: ProcessGroupIdentity;
26
+ cleanupDirectories: CleanupDirectoryIdentity[];
12
27
  };
13
28
 
14
29
  type GuardState = {
15
30
  child?: ChildProcess | undefined;
31
+ jobs: Map<string, GuardJob>;
32
+ };
33
+
34
+ type SpawnRollback = {
35
+ child: ChildProcess;
36
+ marker: string;
37
+ processGroup?: ProcessGroupIdentity | undefined;
38
+ cleanupDirectories?: CleanupDirectoryIdentity[] | undefined;
16
39
  };
17
40
 
18
41
  const MODULE_PATH = fileURLToPath(import.meta.url);
19
42
  const GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);
20
43
  const RUN_ID = randomBytes(8).toString("hex");
21
44
  const RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;
22
- const guard: GuardState = {};
45
+ const guard: GuardState = { jobs: new Map() };
46
+
47
+ const writeGuardCommand = (child: ChildProcess, operation: "+" | "-", job: GuardJob): void => {
48
+ child.stdin?.write(`${operation}${JSON.stringify(job)}\n`);
49
+ };
23
50
 
24
51
  const releaseGuard = (child: ChildProcess): void => {
25
52
  child.unref();
@@ -41,13 +68,62 @@ const startGuard = (): void => {
41
68
  });
42
69
 
43
70
  child.on("error", () => {
44
- guard.child = undefined;
71
+ if (guard.child === child) {
72
+ guard.child = undefined;
73
+ }
74
+ });
75
+
76
+ child.on("exit", () => {
77
+ if (guard.child === child) {
78
+ guard.child = undefined;
79
+ }
80
+ });
81
+
82
+ child.stdin.on("error", () => {
83
+ if (guard.child === child) {
84
+ guard.child = undefined;
85
+ }
45
86
  });
46
87
 
47
88
  guard.child = child;
89
+
90
+ for (const job of guard.jobs.values()) {
91
+ writeGuardCommand(child, "+", job);
92
+ }
93
+
48
94
  releaseGuard(child);
49
95
  };
50
96
 
97
+ const registerJob = (job: GuardJob): void => {
98
+ guard.jobs.set(job.marker, job);
99
+
100
+ if (guard.child) {
101
+ writeGuardCommand(guard.child, "+", job);
102
+ }
103
+ };
104
+
105
+ const unregisterJob = (job: GuardJob): void => {
106
+ guard.jobs.delete(job.marker);
107
+
108
+ if (guard.child) {
109
+ writeGuardCommand(guard.child, "-", job);
110
+ }
111
+ };
112
+
113
+ const rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] }: SpawnRollback): void => {
114
+ if (processGroup === undefined) {
115
+ child.kill("SIGKILL");
116
+ } else {
117
+ killProcessGroup(processGroup);
118
+ }
119
+
120
+ killMarkedProcesses(marker);
121
+
122
+ for (const identity of cleanupDirectories) {
123
+ removeCleanupDirectory(identity);
124
+ }
125
+ };
126
+
51
127
  function spawnWithParentDeathSignal(
52
128
  command: string,
53
129
  args: string[],
@@ -56,6 +132,7 @@ function spawnWithParentDeathSignal(
56
132
  const executable = resolveExecutable(command);
57
133
  const setpriv = resolveExecutable("setpriv");
58
134
  const jobValue = `${RUN_ID}/${randomBytes(4).toString("hex")}`;
135
+ const marker = `${PROCESS_MARKER}=${jobValue}`;
59
136
  startGuard();
60
137
 
61
138
  const child = spawn(setpriv, ["--pdeathsig", "SIGKILL", executable, ...args], {
@@ -64,8 +141,34 @@ function spawnWithParentDeathSignal(
64
141
  env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
65
142
  });
66
143
 
144
+ const processGroupId = child.pid;
145
+ const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);
146
+
147
+ if (group === undefined) {
148
+ rollbackSpawn({ child, marker });
149
+ throw new Error(`Failed to identify process group for ${command}`);
150
+ }
151
+
152
+ const cleanupDirectories = (options.cleanupDirectories ?? []).map((path) => cleanupDirectoryIdentity(path));
153
+
154
+ if (cleanupDirectories.includes(undefined)) {
155
+ const captured = cleanupDirectories.filter(
156
+ (identity): identity is CleanupDirectoryIdentity => identity !== undefined,
157
+ );
158
+ rollbackSpawn({ child, marker, processGroup: group, cleanupDirectories: captured });
159
+ throw new Error(`Failed to identify a cleanup directory for ${command}`);
160
+ }
161
+
162
+ const job: GuardJob = {
163
+ marker,
164
+ processGroup: group,
165
+ cleanupDirectories: cleanupDirectories.filter((identity) => identity !== undefined),
166
+ };
167
+ registerJob(job);
168
+
67
169
  child.on("exit", () => {
68
- killMarkedProcesses(`${PROCESS_MARKER}=${jobValue}`);
170
+ killMarkedProcesses(job.marker);
171
+ unregisterJob(job);
69
172
  });
70
173
 
71
174
  return child;
@@ -1,8 +0,0 @@
1
- type GetSet<K, V> = {
2
- get(key: K): V | undefined;
3
- set(key: K, value: V): unknown;
4
- has(key: K): boolean;
5
- };
6
- declare function getOrInsert<K, V>(map: GetSet<K, V>, key: K, factory: (key: K) => V): V;
7
- export { getOrInsert };
8
- //# sourceMappingURL=get-or-insert.d.ts.map
@@ -1 +0,0 @@
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,10 +0,0 @@
1
- function getOrInsert(map, key, factory) {
2
- if (map.has(key)) {
3
- return map.get(key);
4
- }
5
- const value = factory(key);
6
- map.set(key, value);
7
- return value;
8
- }
9
- export { getOrInsert };
10
- //# sourceMappingURL=get-or-insert.js.map
@@ -1 +0,0 @@
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,2 +0,0 @@
1
- export { getOrInsert } from "./get-or-insert.ts";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/map/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/map/index.js DELETED
@@ -1,2 +0,0 @@
1
- export { getOrInsert } from "./get-or-insert.js";
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/map/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC","sourcesContent":["export { getOrInsert } from \"./get-or-insert.ts\";\n"]}
@@ -1,18 +0,0 @@
1
- type GetSet<K, V> = {
2
- get(key: K): V | undefined;
3
- set(key: K, value: V): unknown;
4
- has(key: K): boolean;
5
- };
6
-
7
- function getOrInsert<K, V>(map: GetSet<K, V>, key: K, factory: (key: K) => V): V {
8
- if (map.has(key)) {
9
- return map.get(key) as V;
10
- }
11
-
12
- const value = factory(key);
13
- map.set(key, value);
14
-
15
- return value;
16
- }
17
-
18
- export { getOrInsert };
package/src/map/index.ts DELETED
@@ -1 +0,0 @@
1
- export { getOrInsert } from "./get-or-insert.ts";