@webpod/ps 1.1.0 → 1.2.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
@@ -2,163 +2,104 @@
2
2
 
3
3
  > A Node.js module for looking up running processes. Originated from [neekey/ps](https://github.com/neekey/ps), [UmbraEngineering/ps](https://github.com/UmbraEngineering/ps) and completely reforged.
4
4
 
5
- ## Differences
6
- * [x] Rewritten in TypeScript
7
- * [x] CJS and ESM package entry points
8
- * [x] `table-parser` replaced with `@webpod/ingrid` to handle some issues: [neekey/ps#76](https://github.com/neekey/ps/issues/76), [neekey/ps#62](https://github.com/neekey/ps/issues/62), [neekey/table-parser#11](https://github.com/neekey/table-parser/issues/11), [neekey/table-parser#18](https://github.com/neekey/table-parser/issues/18)
9
- * [x] Provides promisified responses
10
- * [x] Brings sync API
11
- * [x] Builds a process subtree by parent
5
+ ## Features
6
+ - Written in TypeScript, ships with types
7
+ - CJS and ESM entry points
8
+ - Promise and callback API, sync variants
9
+ - Process tree traversal by parent pid
10
+ - Uses `@webpod/ingrid` instead of `table-parser` ([neekey/ps#76](https://github.com/neekey/ps/issues/76), [neekey/ps#62](https://github.com/neekey/ps/issues/62))
12
11
 
13
12
  ## Install
14
13
  ```bash
15
- $ npm install @webpod/ps
14
+ npm install @webpod/ps
16
15
  ```
17
16
 
18
17
  ## Internals
19
- This module uses different approaches for getting process list:
20
18
 
21
- | Platform | Method |
22
- |--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
23
- | Unix/Mac | `ps -lx` |
24
- | Windows (kernel >= 26000)| `pwsh -NoProfile -Command "Get-CimInstance Win32_Process \| Select-Object ProcessId,ParentProcessId,CommandLine \| ConvertTo-Json -Compress"` |
25
- | Windows (kernel < 26000) | [`wmic`](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmic) `process get ProcessId,CommandLine` |
19
+ | Platform | Command |
20
+ |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
21
+ | Unix / macOS | `ps -eo pid,ppid,args` |
22
+ | Windows (kernel >= 26000) | `pwsh -NoProfile -Command "Get-CimInstance Win32_Process \| Select-Object ProcessId,ParentProcessId,CommandLine \| ConvertTo-Json -Compress"` |
23
+ | Windows (kernel < 26000) | [`wmic`](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmic) `process get ProcessId,CommandLine` |
26
24
 
27
- ## Usage
25
+ ## API
26
+
27
+ ### lookup(query?, callback?)
28
+ Returns a list of processes matching the query.
28
29
 
29
- ### lookup()
30
- Searches for the process by the specified `pid`.
31
30
  ```ts
32
- import {lookup} from '@webpod/ps'
33
-
34
- // Both callback and promise styles are supported
35
- const list = await lookup({pid: 12345})
36
-
37
- // or
38
- lookup({pid: 12345}, (err, list) => {
39
- if (err) {
40
- throw new Error(err)
41
- }
42
-
43
- const [found] = list
44
- if (found) {
45
- console.log('PID: %s, COMMAND: %s, ARGUMENTS: %s', found.pid, found.command, found.arguments)
46
- } else {
47
- console.log('No such process found!')
48
- }
49
- })
31
+ import { lookup } from '@webpod/ps'
50
32
 
51
- // or syncronously
52
- const _list = lookup.sync({pid: 12345})
53
- ```
33
+ // Find by pid
34
+ const list = await lookup({ pid: 12345 })
35
+ // [{ pid: '12345', ppid: '123', command: '/usr/bin/node', arguments: ['server.js', '--port=3000'] }]
54
36
 
55
- Define a query opts to filter the results by `command` and/or `arguments` predicates:
56
- ```ts
57
- const list = await lookup({
58
- command: 'node', // it will be used to build a regex
59
- arguments: '--debug',
60
- })
37
+ // Filter by command and/or arguments (treated as RegExp)
38
+ const nodes = await lookup({ command: 'node', arguments: '--debug' })
61
39
 
62
- list.forEach(entry => {
63
- console.log('PID: %s, COMMAND: %s, ARGUMENTS: %s', entry.pid, entry.command, entry.arguments);
64
- })
40
+ // Filter by parent pid
41
+ const children = await lookup({ ppid: 82292 })
42
+
43
+ // Synchronous
44
+ const all = lookup.sync()
65
45
  ```
66
46
 
67
- Unix users can override the default `ps` arguments:
47
+ On Unix, you can override the default `ps` arguments via `psargs`:
68
48
  ```ts
69
- lookup({
70
- command: 'node',
71
- psargs: 'ux'
72
- }, (err, resultList) => {
73
- // ...
74
- })
49
+ const list = await lookup({ command: 'node', psargs: '-eo pid,ppid,comm' })
75
50
  ```
76
51
 
77
- Specify the `ppid` option to filter the results by the parent process id (make sure that your custom `psargs` provides this output: `-l` or `-j` for instance)
52
+ Callback style is also supported:
78
53
  ```ts
79
- lookup({
80
- command: 'mongod',
81
- psargs: '-l',
82
- ppid: 82292
83
- }, (err, resultList) => {
84
- // ...
85
- })
54
+ lookup({ pid: 12345 }, (err, list) => { /* ... */ })
86
55
  ```
87
56
 
88
- ### tree()
89
- Returns a child processes list by the specified parent `pid`. Some kind of shortcut for `lookup({ppid: pid})`.
57
+ ### tree(opts?, callback?)
58
+ Returns child processes of a given parent pid.
59
+
90
60
  ```ts
91
61
  import { tree } from '@webpod/ps'
92
62
 
93
- const children = await tree(123)
94
- /**
95
- [
96
- {pid: 124, ppid: 123},
97
- {pid: 125, ppid: 123}
98
- ]
99
- */
63
+ // Direct children
64
+ const children = await tree(123)
65
+ // [
66
+ // { pid: '124', ppid: '123', command: 'node', arguments: ['worker.js'] },
67
+ // { pid: '125', ppid: '123', command: 'node', arguments: ['worker.js'] }
68
+ // ]
69
+
70
+ // All descendants
71
+ const all = await tree({ pid: 123, recursive: true })
72
+ // [
73
+ // { pid: '124', ppid: '123', ... },
74
+ // { pid: '125', ppid: '123', ... },
75
+ // { pid: '126', ppid: '124', ... },
76
+ // { pid: '127', ppid: '125', ... }
77
+ // ]
78
+
79
+ // Synchronous
80
+ const list = tree.sync({ pid: 123, recursive: true })
100
81
  ```
101
82
 
102
- To obtain all nested children, set `recursive` option to `true`:
103
- ```ts
104
- const children = await tree({pid: 123, recursive: true})
105
- /**
106
- [
107
- {pid: 124, ppid: 123},
108
- {pid: 125, ppid: 123},
109
-
110
- {pid: 126, ppid: 124},
111
- {pid: 127, ppid: 124},
112
- {pid: 128, ppid: 124},
113
-
114
- {pid: 129, ppid: 125},
115
- {pid: 130, ppid: 125},
116
- ]
117
- */
118
-
119
- // or syncronously
120
- const list = tree.sync({pid: 123, recursive: true})
121
- ```
122
-
123
- ### kill()
124
- Eliminates the process by its `pid`.
83
+ ### kill(pid, opts?, callback?)
84
+ Kills a process and waits for it to exit. The returned promise resolves once the process is confirmed dead, or rejects on timeout.
125
85
 
126
86
  ```ts
127
87
  import { kill } from '@webpod/ps'
128
88
 
129
- kill('12345', (err, pid) => {
130
- if (err) {
131
- throw new Error(err)
132
- } else {
133
- console.log('Process %s has been killed!', pid)
134
- }
135
- })
136
- ```
89
+ // Sends SIGTERM, polls until the process is gone (default timeout 30s)
90
+ await kill(12345)
137
91
 
138
- Method `kill` also supports a `signal` option to be passed. It's only a wrapper of `process.kill()` with checking of that killing is finished after the method is called.
92
+ // With signal
93
+ await kill(12345, 'SIGKILL')
139
94
 
140
- ```ts
141
- import { kill } from '@webpod/ps'
95
+ // With custom timeout (seconds) and polling interval (ms)
96
+ await kill(12345, { signal: 'SIGKILL', timeout: 10, interval: 250 })
142
97
 
143
- // Pass signal SIGKILL for killing the process without allowing it to clean up
144
- kill('12345', 'SIGKILL', (err, pid) => {
145
- if (err) {
146
- throw new Error(err)
147
- } else {
148
- console.log('Process %s has been killed without a clean-up!', pid)
149
- }
98
+ // With callback
99
+ await kill(12345, (err, pid) => {
100
+ // called when the process is confirmed dead or timeout is reached
150
101
  })
151
102
  ```
152
103
 
153
- You can also use object notation to specify more opts:
154
- ```ts
155
- kill( '12345', {
156
- signal: 'SIGKILL',
157
- timeout: 10, // will set up a ten seconds timeout if the killing is not successful
158
- }, () => {})
159
- ```
160
-
161
- Notice that the nodejs build-in `process.kill()` does not accept number as a signal, you will have to use string format.
162
-
163
104
  ## License
164
105
  [MIT](./LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpod/ps",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A process lookup utility",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,12 +23,23 @@
23
23
  "build:dts": "tsc --emitDeclarationOnly --outDir target/dts",
24
24
  "build:docs": "typedoc --options src/main/typedoc",
25
25
  "build:stamp": "npx buildstamp",
26
- "test": "concurrently 'npm:test:lint' 'npm:test:unit' && npm run test:legacy",
26
+ "test": "concurrently 'npm:test:lint' 'npm:test:unit' 'npm:test:size' && npm run test:legacy",
27
27
  "test:lint": "oxlint -c oxlintrc.json src/main/ts src/test/ts",
28
28
  "test:unit": "c8 -r lcov -r text -o target/coverage -x src/scripts -x src/test -x target node --loader ts-node/esm --experimental-specifier-resolution=node src/scripts/test.mjs",
29
29
  "test:legacy": "node ./node_modules/mocha/bin/mocha -t 0 -R spec src/test/legacy/test.cjs",
30
+ "test:size": "size-limit",
30
31
  "publish:draft": "npm run build && npm publish --no-git-tag-version"
31
32
  },
33
+ "size-limit": [
34
+ {
35
+ "path": "target/esm/index.mjs",
36
+ "limit": "4 kB"
37
+ },
38
+ {
39
+ "path": "target/cjs/index.cjs",
40
+ "limit": "5 kB"
41
+ }
42
+ ],
32
43
  "files": [
33
44
  "target/cjs",
34
45
  "target/esm",
@@ -45,17 +56,18 @@
45
56
  "zurk": "^0.11.10"
46
57
  },
47
58
  "devDependencies": {
59
+ "@size-limit/file": "^12.0.1",
48
60
  "@types/node": "^25.5.2",
49
61
  "c8": "^11.0.0",
50
62
  "concurrently": "^9.2.1",
51
63
  "esbuild": "^0.28.0",
52
64
  "esbuild-node-externals": "^1.21.0",
53
65
  "esbuild-plugin-entry-chunks": "^0.1.18",
54
- "oxlint": "^1.58.0",
55
66
  "fast-glob": "^3.3.3",
56
67
  "minimist": "^1.2.8",
57
- "mocha": "^10.8.2",
58
- "sinon": "^18.0.1",
68
+ "mocha": "^11.7.5",
69
+ "oxlint": "^1.58.0",
70
+ "size-limit": "^12.0.1",
59
71
  "ts-node": "^10.9.2",
60
72
  "typedoc": "^0.28.18",
61
73
  "typescript": "^5.9.2"
@@ -71,209 +71,181 @@ var LOOKUPS = {
71
71
  wmic: {
72
72
  cmd: "wmic process get ProcessId,ParentProcessId,CommandLine",
73
73
  args: [],
74
- parse(stdout) {
75
- return (0, import_ingrid.parse)(removeWmicPrefix(stdout), { format: "win" });
76
- }
74
+ parse: (stdout) => (0, import_ingrid.parse)(removeWmicPrefix(stdout), { format: "win" })
77
75
  },
78
76
  ps: {
79
77
  cmd: "ps",
80
78
  args: ["-eo", "pid,ppid,args"],
81
- parse(stdout) {
82
- return (0, import_ingrid.parse)(stdout, { format: "unix" });
83
- }
79
+ parse: (stdout) => (0, import_ingrid.parse)(stdout, { format: "unix" })
84
80
  },
85
81
  pwsh: {
86
82
  cmd: "pwsh",
87
83
  args: ["-NoProfile", "-Command", '"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine | ConvertTo-Json -Compress"'],
88
84
  parse(stdout) {
89
- let arr = [];
90
85
  try {
91
- arr = JSON.parse(stdout);
86
+ const arr = JSON.parse(stdout);
87
+ return arr.map((p) => ({
88
+ ProcessId: [String(p.ProcessId)],
89
+ ParentProcessId: [String(p.ParentProcessId)],
90
+ CommandLine: p.CommandLine ? [p.CommandLine] : []
91
+ }));
92
92
  } catch (e) {
93
93
  return [];
94
94
  }
95
- return arr.map((p) => ({
96
- ProcessId: [p.ProcessId + ""],
97
- ParentProcessId: [p.ParentProcessId + ""],
98
- CommandLine: p.CommandLine ? [p.CommandLine] : []
99
- }));
100
95
  }
101
96
  }
102
97
  };
103
- var isBin = (f) => {
104
- if (f === "") return false;
105
- if (!f.includes("/") && !f.includes("\\")) return true;
106
- if (f.length > 3 && f[0] === '"')
107
- return f[f.length - 1] === '"' ? isBin(f.slice(1, -1)) : false;
108
- try {
109
- if (!import_node_fs.default.existsSync(f)) return false;
110
- const stat = import_node_fs.default.lstatSync(f);
111
- return stat.isFile() || stat.isSymbolicLink();
112
- } catch (e) {
113
- return false;
114
- }
115
- };
98
+ var lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
116
99
  var lookup = (query = {}, cb = noop) => _lookup({ query, cb, sync: false });
117
100
  var lookupSync = (query = {}, cb = noop) => _lookup({ query, cb, sync: true });
118
101
  lookup.sync = lookupSync;
119
- var _lookup = ({
120
- query = {},
121
- cb = noop,
122
- sync = false
123
- }) => {
124
- const pFactory = sync ? makePseudoDeferred.bind(null, []) : makeDeferred;
125
- const { promise, resolve, reject } = pFactory();
102
+ var _lookup = ({ query = {}, cb = noop, sync = false }) => {
103
+ const { promise, resolve, reject } = sync ? makeSyncDeferred([]) : makeDeferred();
126
104
  const result = [];
127
- const lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
128
- const { parse: parse2, cmd, args } = LOOKUPS[lookupFlow];
105
+ const { parse: parseOutput, cmd, args: defaultArgs } = LOOKUPS[lookupFlow];
106
+ const args = !IS_WIN && query.psargs ? query.psargs.split(/\s+/) : defaultArgs;
129
107
  const callback = (err, { stdout }) => {
130
108
  if (err) {
131
109
  reject(err);
132
110
  cb(err);
133
111
  return;
134
112
  }
135
- result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
113
+ result.push(...filterProcessList(normalizeOutput(parseOutput(stdout)), query));
136
114
  resolve(result);
137
115
  cb(null, result);
138
116
  };
139
- (0, import_spawn.exec)({
140
- cmd,
141
- args,
142
- callback,
143
- sync,
144
- run(cb2) {
145
- cb2();
146
- }
147
- });
117
+ (0, import_spawn.exec)({ cmd, args, callback, sync, run(cb2) {
118
+ cb2();
119
+ } });
148
120
  return Object.assign(promise, result);
149
121
  };
150
- var filterProcessList = (processList, query = {}) => {
151
- const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
152
- const filters = [
153
- (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
154
- (p) => query.arguments ? new RegExp(query.arguments, "i").test(p.arguments.join(" ")) : true,
155
- (p) => query.ppid ? query.ppid + "" === p.ppid : true
156
- ];
157
- return processList.filter(
158
- (p) => (pidList.length === 0 || pidList.includes(p.pid)) && filters.every((f) => f(p))
159
- );
160
- };
161
- var removeWmicPrefix = (stdout) => {
162
- const s = stdout.indexOf(LOOKUPS.wmic.cmd + import_node_os.default.EOL);
163
- const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(import_node_os.default.EOL) : stdout.length;
164
- return (s > 0 ? stdout.slice(s + LOOKUPS.wmic.cmd.length, e) : stdout.slice(0, e)).trimStart();
165
- };
166
- var pickTree = (list, pid, recursive = false) => {
167
- const children = list.filter((p) => p.ppid === pid + "");
168
- return [
169
- ...children,
170
- ...children.flatMap((p) => recursive ? pickTree(list, p.pid, true) : [])
171
- ];
172
- };
173
- var _tree = ({
174
- cb = noop,
175
- opts,
176
- sync = false
177
- }) => {
122
+ var tree = (opts, cb) => __async(null, null, function* () {
123
+ return _tree({ opts, cb });
124
+ });
125
+ var treeSync = (opts, cb) => _tree({ opts, cb, sync: true });
126
+ tree.sync = treeSync;
127
+ var _tree = ({ cb = noop, opts, sync = false }) => {
178
128
  if (typeof opts === "string" || typeof opts === "number") {
179
129
  return _tree({ opts: { pid: opts }, cb, sync });
180
130
  }
181
- const onError = (err) => cb(err);
182
131
  const onData = (all) => {
132
+ var _a;
183
133
  if (opts === void 0) return all;
184
- const { pid, recursive = false } = opts;
185
- const list = pickTree(all, pid, recursive);
134
+ const list = pickTree(all, opts.pid, (_a = opts.recursive) != null ? _a : false);
186
135
  cb(null, list);
187
136
  return list;
188
137
  };
138
+ const onError = (err) => {
139
+ cb(err);
140
+ throw err;
141
+ };
189
142
  try {
190
143
  const all = _lookup({ sync });
191
- return sync ? onData(all) : all.then(onData, (err) => {
192
- onError(err);
193
- throw err;
194
- });
144
+ return sync ? onData(all) : all.then(onData, onError);
195
145
  } catch (err) {
196
- onError(err);
146
+ cb(err);
197
147
  return Promise.reject(err);
198
148
  }
199
149
  };
200
- var tree = (opts, cb) => __async(null, null, function* () {
201
- return _tree({ opts, cb });
202
- });
203
- var treeSync = (opts, cb) => _tree({ opts, cb, sync: true });
204
- tree.sync = treeSync;
150
+ var inflight = null;
151
+ var queued = null;
152
+ var sharedSnapshot = (since) => {
153
+ var _a;
154
+ if (inflight && inflight.startedAt >= since) return inflight.promise;
155
+ if (queued) return queued;
156
+ const after = (_a = inflight == null ? void 0 : inflight.promise.catch(noop)) != null ? _a : Promise.resolve();
157
+ return queued = after.then(() => {
158
+ queued = null;
159
+ const startedAt = Date.now();
160
+ const promise = lookup().then((list) => ({ startedAt, list }));
161
+ inflight = { startedAt, promise };
162
+ return promise.finally(() => {
163
+ inflight = (inflight == null ? void 0 : inflight.promise) === promise ? null : inflight;
164
+ });
165
+ });
166
+ };
167
+ var pickTree = (list, pid, recursive = false) => {
168
+ const children = list.filter((p) => p.ppid === String(pid));
169
+ return [
170
+ ...children,
171
+ ...children.flatMap((p) => recursive ? pickTree(list, p.pid, true) : [])
172
+ ];
173
+ };
205
174
  var kill = (pid, opts, next) => {
206
- if (typeof opts == "function") {
207
- return kill(pid, void 0, opts);
208
- }
209
- if (typeof opts == "string" || typeof opts == "number") {
210
- return kill(pid, { signal: opts }, next);
211
- }
175
+ if (typeof opts === "function") return kill(pid, void 0, opts);
176
+ if (typeof opts === "string" || typeof opts === "number") return kill(pid, { signal: opts }, next);
212
177
  const { promise, resolve, reject } = makeDeferred();
213
- const {
214
- timeout = 30,
215
- signal = "SIGTERM"
216
- } = opts || {};
178
+ const { timeout = 30, signal = "SIGTERM", interval = 200 } = opts || {};
179
+ const sPid = String(pid);
180
+ let done = false;
181
+ const state = {};
182
+ const settle = (err) => {
183
+ if (done) return;
184
+ done = true;
185
+ clearTimeout(state.timer);
186
+ if (err) reject(err);
187
+ else resolve(pid);
188
+ next == null ? void 0 : next(err != null ? err : null, pid);
189
+ };
217
190
  try {
218
191
  import_node_process.default.kill(+pid, signal);
219
192
  } catch (e) {
220
- reject(e);
221
- next == null ? void 0 : next(e);
193
+ settle(e);
222
194
  return promise;
223
195
  }
224
- let checkConfident = 0;
225
- let checkTimeoutTimer;
226
- let checkIsTimeout = false;
227
- const checkKilled = (finishCallback) => lookup({ pid }, (err, list = []) => {
228
- if (checkIsTimeout) return;
229
- if (err) {
230
- clearTimeout(checkTimeoutTimer);
231
- reject(err);
232
- finishCallback == null ? void 0 : finishCallback(err, pid);
233
- } else if (list.length > 0) {
234
- checkConfident = checkConfident - 1 || 0;
235
- checkKilled(finishCallback);
196
+ let since = Date.now();
197
+ state.timer = setTimeout(() => settle(new Error("Kill process timeout")), timeout * 1e3);
198
+ const poll = () => sharedSnapshot(since).then(({ startedAt, list }) => {
199
+ if (done) return;
200
+ since = startedAt + 1;
201
+ if (list.some((p) => p.pid === sPid)) {
202
+ setTimeout(poll, Math.max(0, startedAt + interval - Date.now()));
236
203
  } else {
237
- checkConfident++;
238
- if (checkConfident === 5) {
239
- clearTimeout(checkTimeoutTimer);
240
- resolve(pid);
241
- finishCallback == null ? void 0 : finishCallback(null, pid);
242
- } else {
243
- checkKilled(finishCallback);
244
- }
204
+ settle();
245
205
  }
246
- });
247
- if (next) {
248
- checkKilled(next);
249
- checkTimeoutTimer = setTimeout(() => {
250
- checkIsTimeout = true;
251
- next(new Error("Kill process timeout"));
252
- }, timeout * 1e3);
253
- } else {
254
- resolve(pid);
255
- }
206
+ }, settle);
207
+ poll();
256
208
  return promise;
257
209
  };
258
- var normalizeOutput = (data) => data.reduce((m, d) => {
210
+ var normalizeOutput = (data) => data.flatMap((d) => {
259
211
  var _a, _b;
260
212
  const pid = (_a = d.PID || d.ProcessId) == null ? void 0 : _a[0];
261
213
  const ppid = (_b = d.PPID || d.ParentProcessId) == null ? void 0 : _b[0];
262
- const _cmd = d.CMD || d.CommandLine || d.COMMAND || d.ARGS || [];
263
- const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
264
- if (pid && cmd.length > 0) {
265
- const c = cmd.findIndex((_v, i) => isBin(cmd.slice(0, i).join(" ")));
266
- const command = (c === -1 ? cmd : cmd.slice(0, c)).join(" ");
267
- const args = c === -1 ? [] : cmd.slice(c);
268
- m.push({
269
- pid,
270
- ppid,
271
- command,
272
- arguments: args
273
- });
214
+ const rawCmd = d.CMD || d.CommandLine || d.COMMAND || d.ARGS || [];
215
+ const parts = rawCmd.length === 1 ? rawCmd[0].split(/\s+/) : rawCmd;
216
+ if (!pid || parts.length === 0) return [];
217
+ const binIdx = parts.findIndex((_v, i) => isBin(parts.slice(0, i).join(" ")));
218
+ const command = (binIdx === -1 ? parts : parts.slice(0, binIdx)).join(" ");
219
+ const args = binIdx === -1 ? [] : parts.slice(binIdx);
220
+ return [{ pid, ppid, command, arguments: args }];
221
+ });
222
+ var filterProcessList = (processList, query = {}) => {
223
+ const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map(String);
224
+ const commandRe = query.command ? new RegExp(query.command, "i") : null;
225
+ const argumentsRe = query.arguments ? new RegExp(query.arguments, "i") : null;
226
+ const ppid = query.ppid === void 0 ? null : String(query.ppid);
227
+ return processList.filter(
228
+ (p) => (pidList.length === 0 || pidList.includes(p.pid)) && (!commandRe || commandRe.test(p.command)) && (!argumentsRe || argumentsRe.test(p.arguments.join(" "))) && (!ppid || ppid === p.ppid)
229
+ );
230
+ };
231
+ var removeWmicPrefix = (stdout) => {
232
+ const s = stdout.indexOf(LOOKUPS.wmic.cmd + import_node_os.default.EOL);
233
+ const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(import_node_os.default.EOL) : stdout.length;
234
+ return (s > 0 ? stdout.slice(s + LOOKUPS.wmic.cmd.length, e) : stdout.slice(0, e)).trimStart();
235
+ };
236
+ var isBin = (f) => {
237
+ if (f === "") return false;
238
+ if (!f.includes("/") && !f.includes("\\")) return true;
239
+ if (f.length > 3 && f[0] === '"')
240
+ return f.at(-1) === '"' ? isBin(f.slice(1, -1)) : false;
241
+ try {
242
+ if (!import_node_fs.default.existsSync(f)) return false;
243
+ const stat = import_node_fs.default.lstatSync(f);
244
+ return stat.isFile() || stat.isSymbolicLink();
245
+ } catch (e) {
246
+ return false;
274
247
  }
275
- return m;
276
- }, []);
248
+ };
277
249
  var makeDeferred = () => {
278
250
  let resolve;
279
251
  let reject;
@@ -283,16 +255,16 @@ var makeDeferred = () => {
283
255
  });
284
256
  return { resolve, reject, promise };
285
257
  };
286
- var makePseudoDeferred = (r = {}) => ({
287
- promise: r,
288
- resolve: identity,
258
+ var makeSyncDeferred = (result) => ({
259
+ promise: result,
260
+ resolve: () => {
261
+ },
289
262
  reject(e) {
290
263
  throw e;
291
264
  }
292
265
  });
293
266
  var noop = () => {
294
267
  };
295
- var identity = (v) => v;
296
268
 
297
269
  // src/main/ts/index.ts
298
270
  var index_default = { kill, lookup, lookupSync, tree, treeSync };
@@ -8,9 +8,9 @@ declare const _default: {
8
8
  };
9
9
  lookupSync: (query?: import("./ps.js").TPsLookupQuery, cb?: import("./ps.js").TPsLookupCallback) => import("./ps.js").TPsLookupEntry[];
10
10
  tree: {
11
- (opts?: string | number | import("./ps.js").TPsTreeOpts | undefined, cb?: import("./ps.js").TPsLookupCallback): Promise<import("./ps.js").TPsLookupEntry[]>;
12
- sync: (opts?: string | number | import("./ps.js").TPsTreeOpts | undefined, cb?: import("./ps.js").TPsLookupCallback) => import("./ps.js").TPsLookupEntry[];
11
+ (opts?: string | number | import("./ps.js").TPsTreeOpts, cb?: import("./ps.js").TPsLookupCallback): Promise<import("./ps.js").TPsLookupEntry[]>;
12
+ sync: (opts?: string | number | import("./ps.js").TPsTreeOpts, cb?: import("./ps.js").TPsLookupCallback) => import("./ps.js").TPsLookupEntry[];
13
13
  };
14
- treeSync: (opts?: string | number | import("./ps.js").TPsTreeOpts | undefined, cb?: import("./ps.js").TPsLookupCallback) => import("./ps.js").TPsLookupEntry[];
14
+ treeSync: (opts?: string | number | import("./ps.js").TPsTreeOpts, cb?: import("./ps.js").TPsLookupCallback) => import("./ps.js").TPsLookupEntry[];
15
15
  };
16
16
  export default _default;
@@ -1,5 +1,4 @@
1
1
  import { type TIngridResponse } from '@webpod/ingrid';
2
- export type TPsLookupCallback = (err: any, processList?: TPsLookupEntry[]) => void;
3
2
  export type TPsLookupEntry = {
4
3
  pid: string;
5
4
  ppid?: string;
@@ -11,57 +10,45 @@ export type TPsLookupQuery = {
11
10
  command?: string;
12
11
  arguments?: string;
13
12
  ppid?: number | string;
13
+ psargs?: string;
14
14
  };
15
+ export type TPsLookupCallback = (err: any, processList?: TPsLookupEntry[]) => void;
15
16
  export type TPsKillOptions = {
16
17
  timeout?: number;
17
18
  signal?: string | number | NodeJS.Signals;
19
+ /** Polling interval in ms between exit checks (default 200). */
20
+ interval?: number;
21
+ };
22
+ export type TPsTreeOpts = {
23
+ pid: string | number;
24
+ recursive?: boolean;
18
25
  };
19
26
  export type TPsNext = (err?: any, data?: any) => void;
20
27
  /**
21
- * Query Process: Focus on pid & cmd
22
- * @param query
23
- * @param {String|String[]} query.pid
24
- * @param {String} query.command RegExp String
25
- * @param {String} query.arguments RegExp String
26
- * @param {String|String[]} query.psargs
27
- * @param {TPsLookupCallback} cb
28
- * @return {Promise<TPsLookupEntry[]>}
28
+ * Query running processes by pid, command, arguments or ppid.
29
+ * Supports both promise and callback styles.
29
30
  */
30
31
  export declare const lookup: {
31
32
  (query?: TPsLookupQuery, cb?: TPsLookupCallback): Promise<TPsLookupEntry[]>;
32
33
  sync: (query?: TPsLookupQuery, cb?: TPsLookupCallback) => TPsLookupEntry[];
33
34
  };
34
- /**
35
- * Looks up the process list synchronously
36
- * @param query
37
- * @param {String|String[]} query.pid
38
- * @param {String} query.command RegExp String
39
- * @param {String} query.arguments RegExp String
40
- * @param {String|String[]} query.psargs
41
- * @param {TPsLookupCallback} cb
42
- * @return {TPsLookupEntry[]}
43
- */
35
+ /** Synchronous version of {@link lookup}. */
44
36
  export declare const lookupSync: (query?: TPsLookupQuery, cb?: TPsLookupCallback) => TPsLookupEntry[];
45
- export declare const filterProcessList: (processList: TPsLookupEntry[], query?: TPsLookupQuery) => TPsLookupEntry[];
46
- export declare const removeWmicPrefix: (stdout: string) => string;
47
- export type TPsTreeOpts = {
48
- pid: string | number;
49
- recursive?: boolean;
50
- };
51
- export declare const pickTree: (list: TPsLookupEntry[], pid: string | number, recursive?: boolean) => TPsLookupEntry[];
37
+ /** Returns child processes of the given parent pid. */
52
38
  export declare const tree: {
53
- (opts?: string | number | TPsTreeOpts | undefined, cb?: TPsLookupCallback): Promise<TPsLookupEntry[]>;
54
- sync: (opts?: string | number | TPsTreeOpts | undefined, cb?: TPsLookupCallback) => TPsLookupEntry[];
39
+ (opts?: string | number | TPsTreeOpts, cb?: TPsLookupCallback): Promise<TPsLookupEntry[]>;
40
+ sync: (opts?: string | number | TPsTreeOpts, cb?: TPsLookupCallback) => TPsLookupEntry[];
55
41
  };
56
- export declare const treeSync: (opts?: string | number | TPsTreeOpts | undefined, cb?: TPsLookupCallback) => TPsLookupEntry[];
42
+ /** Synchronous version of {@link tree}. */
43
+ export declare const treeSync: (opts?: string | number | TPsTreeOpts, cb?: TPsLookupCallback) => TPsLookupEntry[];
44
+ export declare const pickTree: (list: TPsLookupEntry[], pid: string | number, recursive?: boolean) => TPsLookupEntry[];
57
45
  /**
58
- * Kill process
59
- * @param pid
60
- * @param {Object|String} opts
61
- * @param {String} opts.signal
62
- * @param {number} opts.timeout
63
- * @param next
46
+ * Kills a process by pid.
47
+ * @param pid - Process ID to kill
48
+ * @param opts - Signal, options object, or callback
49
+ * @param next - Callback invoked when kill is confirmed or timed out
64
50
  */
65
51
  export declare const kill: (pid: string | number, opts?: TPsNext | TPsKillOptions | TPsKillOptions["signal"], next?: TPsNext) => Promise<void>;
66
52
  export declare const normalizeOutput: (data: TIngridResponse) => TPsLookupEntry[];
67
- export type PromiseResolve<T = any> = (value?: T | PromiseLike<T>) => void;
53
+ export declare const filterProcessList: (processList: TPsLookupEntry[], query?: TPsLookupQuery) => TPsLookupEntry[];
54
+ export declare const removeWmicPrefix: (stdout: string) => string;
@@ -10,206 +10,176 @@ var LOOKUPS = {
10
10
  wmic: {
11
11
  cmd: "wmic process get ProcessId,ParentProcessId,CommandLine",
12
12
  args: [],
13
- parse(stdout) {
14
- return parse(removeWmicPrefix(stdout), { format: "win" });
15
- }
13
+ parse: (stdout) => parse(removeWmicPrefix(stdout), { format: "win" })
16
14
  },
17
15
  ps: {
18
16
  cmd: "ps",
19
17
  args: ["-eo", "pid,ppid,args"],
20
- parse(stdout) {
21
- return parse(stdout, { format: "unix" });
22
- }
18
+ parse: (stdout) => parse(stdout, { format: "unix" })
23
19
  },
24
20
  pwsh: {
25
21
  cmd: "pwsh",
26
22
  args: ["-NoProfile", "-Command", '"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine | ConvertTo-Json -Compress"'],
27
23
  parse(stdout) {
28
- let arr = [];
29
24
  try {
30
- arr = JSON.parse(stdout);
25
+ const arr = JSON.parse(stdout);
26
+ return arr.map((p) => ({
27
+ ProcessId: [String(p.ProcessId)],
28
+ ParentProcessId: [String(p.ParentProcessId)],
29
+ CommandLine: p.CommandLine ? [p.CommandLine] : []
30
+ }));
31
31
  } catch {
32
32
  return [];
33
33
  }
34
- return arr.map((p) => ({
35
- ProcessId: [p.ProcessId + ""],
36
- ParentProcessId: [p.ParentProcessId + ""],
37
- CommandLine: p.CommandLine ? [p.CommandLine] : []
38
- }));
39
34
  }
40
35
  }
41
36
  };
42
- var isBin = (f) => {
43
- if (f === "") return false;
44
- if (!f.includes("/") && !f.includes("\\")) return true;
45
- if (f.length > 3 && f[0] === '"')
46
- return f[f.length - 1] === '"' ? isBin(f.slice(1, -1)) : false;
47
- try {
48
- if (!fs.existsSync(f)) return false;
49
- const stat = fs.lstatSync(f);
50
- return stat.isFile() || stat.isSymbolicLink();
51
- } catch {
52
- return false;
53
- }
54
- };
37
+ var lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
55
38
  var lookup = (query = {}, cb = noop) => _lookup({ query, cb, sync: false });
56
39
  var lookupSync = (query = {}, cb = noop) => _lookup({ query, cb, sync: true });
57
40
  lookup.sync = lookupSync;
58
- var _lookup = ({
59
- query = {},
60
- cb = noop,
61
- sync = false
62
- }) => {
63
- const pFactory = sync ? makePseudoDeferred.bind(null, []) : makeDeferred;
64
- const { promise, resolve, reject } = pFactory();
41
+ var _lookup = ({ query = {}, cb = noop, sync = false }) => {
42
+ const { promise, resolve, reject } = sync ? makeSyncDeferred([]) : makeDeferred();
65
43
  const result = [];
66
- const lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
67
- const { parse: parse2, cmd, args } = LOOKUPS[lookupFlow];
44
+ const { parse: parseOutput, cmd, args: defaultArgs } = LOOKUPS[lookupFlow];
45
+ const args = !IS_WIN && query.psargs ? query.psargs.split(/\s+/) : defaultArgs;
68
46
  const callback = (err, { stdout }) => {
69
47
  if (err) {
70
48
  reject(err);
71
49
  cb(err);
72
50
  return;
73
51
  }
74
- result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
52
+ result.push(...filterProcessList(normalizeOutput(parseOutput(stdout)), query));
75
53
  resolve(result);
76
54
  cb(null, result);
77
55
  };
78
- exec({
79
- cmd,
80
- args,
81
- callback,
82
- sync,
83
- run(cb2) {
84
- cb2();
85
- }
86
- });
56
+ exec({ cmd, args, callback, sync, run(cb2) {
57
+ cb2();
58
+ } });
87
59
  return Object.assign(promise, result);
88
60
  };
89
- var filterProcessList = (processList, query = {}) => {
90
- const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
91
- const filters = [
92
- (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
93
- (p) => query.arguments ? new RegExp(query.arguments, "i").test(p.arguments.join(" ")) : true,
94
- (p) => query.ppid ? query.ppid + "" === p.ppid : true
95
- ];
96
- return processList.filter(
97
- (p) => (pidList.length === 0 || pidList.includes(p.pid)) && filters.every((f) => f(p))
98
- );
99
- };
100
- var removeWmicPrefix = (stdout) => {
101
- const s = stdout.indexOf(LOOKUPS.wmic.cmd + os.EOL);
102
- const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(os.EOL) : stdout.length;
103
- return (s > 0 ? stdout.slice(s + LOOKUPS.wmic.cmd.length, e) : stdout.slice(0, e)).trimStart();
104
- };
105
- var pickTree = (list, pid, recursive = false) => {
106
- const children = list.filter((p) => p.ppid === pid + "");
107
- return [
108
- ...children,
109
- ...children.flatMap((p) => recursive ? pickTree(list, p.pid, true) : [])
110
- ];
111
- };
112
- var _tree = ({
113
- cb = noop,
114
- opts,
115
- sync = false
116
- }) => {
61
+ var tree = async (opts, cb) => _tree({ opts, cb });
62
+ var treeSync = (opts, cb) => _tree({ opts, cb, sync: true });
63
+ tree.sync = treeSync;
64
+ var _tree = ({ cb = noop, opts, sync = false }) => {
117
65
  if (typeof opts === "string" || typeof opts === "number") {
118
66
  return _tree({ opts: { pid: opts }, cb, sync });
119
67
  }
120
- const onError = (err) => cb(err);
121
68
  const onData = (all) => {
122
69
  if (opts === void 0) return all;
123
- const { pid, recursive = false } = opts;
124
- const list = pickTree(all, pid, recursive);
70
+ const list = pickTree(all, opts.pid, opts.recursive ?? false);
125
71
  cb(null, list);
126
72
  return list;
127
73
  };
74
+ const onError = (err) => {
75
+ cb(err);
76
+ throw err;
77
+ };
128
78
  try {
129
79
  const all = _lookup({ sync });
130
- return sync ? onData(all) : all.then(onData, (err) => {
131
- onError(err);
132
- throw err;
133
- });
80
+ return sync ? onData(all) : all.then(onData, onError);
134
81
  } catch (err) {
135
- onError(err);
82
+ cb(err);
136
83
  return Promise.reject(err);
137
84
  }
138
85
  };
139
- var tree = async (opts, cb) => _tree({ opts, cb });
140
- var treeSync = (opts, cb) => _tree({ opts, cb, sync: true });
141
- tree.sync = treeSync;
86
+ var inflight = null;
87
+ var queued = null;
88
+ var sharedSnapshot = (since) => {
89
+ if (inflight && inflight.startedAt >= since) return inflight.promise;
90
+ if (queued) return queued;
91
+ const after = inflight?.promise.catch(noop) ?? Promise.resolve();
92
+ return queued = after.then(() => {
93
+ queued = null;
94
+ const startedAt = Date.now();
95
+ const promise = lookup().then((list) => ({ startedAt, list }));
96
+ inflight = { startedAt, promise };
97
+ return promise.finally(() => {
98
+ inflight = inflight?.promise === promise ? null : inflight;
99
+ });
100
+ });
101
+ };
102
+ var pickTree = (list, pid, recursive = false) => {
103
+ const children = list.filter((p) => p.ppid === String(pid));
104
+ return [
105
+ ...children,
106
+ ...children.flatMap((p) => recursive ? pickTree(list, p.pid, true) : [])
107
+ ];
108
+ };
142
109
  var kill = (pid, opts, next) => {
143
- if (typeof opts == "function") {
144
- return kill(pid, void 0, opts);
145
- }
146
- if (typeof opts == "string" || typeof opts == "number") {
147
- return kill(pid, { signal: opts }, next);
148
- }
110
+ if (typeof opts === "function") return kill(pid, void 0, opts);
111
+ if (typeof opts === "string" || typeof opts === "number") return kill(pid, { signal: opts }, next);
149
112
  const { promise, resolve, reject } = makeDeferred();
150
- const {
151
- timeout = 30,
152
- signal = "SIGTERM"
153
- } = opts || {};
113
+ const { timeout = 30, signal = "SIGTERM", interval = 200 } = opts || {};
114
+ const sPid = String(pid);
115
+ let done = false;
116
+ const state = {};
117
+ const settle = (err) => {
118
+ if (done) return;
119
+ done = true;
120
+ clearTimeout(state.timer);
121
+ if (err) reject(err);
122
+ else resolve(pid);
123
+ next?.(err ?? null, pid);
124
+ };
154
125
  try {
155
126
  process.kill(+pid, signal);
156
127
  } catch (e) {
157
- reject(e);
158
- next?.(e);
128
+ settle(e);
159
129
  return promise;
160
130
  }
161
- let checkConfident = 0;
162
- let checkTimeoutTimer;
163
- let checkIsTimeout = false;
164
- const checkKilled = (finishCallback) => lookup({ pid }, (err, list = []) => {
165
- if (checkIsTimeout) return;
166
- if (err) {
167
- clearTimeout(checkTimeoutTimer);
168
- reject(err);
169
- finishCallback?.(err, pid);
170
- } else if (list.length > 0) {
171
- checkConfident = checkConfident - 1 || 0;
172
- checkKilled(finishCallback);
131
+ let since = Date.now();
132
+ state.timer = setTimeout(() => settle(new Error("Kill process timeout")), timeout * 1e3);
133
+ const poll = () => sharedSnapshot(since).then(({ startedAt, list }) => {
134
+ if (done) return;
135
+ since = startedAt + 1;
136
+ if (list.some((p) => p.pid === sPid)) {
137
+ setTimeout(poll, Math.max(0, startedAt + interval - Date.now()));
173
138
  } else {
174
- checkConfident++;
175
- if (checkConfident === 5) {
176
- clearTimeout(checkTimeoutTimer);
177
- resolve(pid);
178
- finishCallback?.(null, pid);
179
- } else {
180
- checkKilled(finishCallback);
181
- }
139
+ settle();
182
140
  }
183
- });
184
- if (next) {
185
- checkKilled(next);
186
- checkTimeoutTimer = setTimeout(() => {
187
- checkIsTimeout = true;
188
- next(new Error("Kill process timeout"));
189
- }, timeout * 1e3);
190
- } else {
191
- resolve(pid);
192
- }
141
+ }, settle);
142
+ poll();
193
143
  return promise;
194
144
  };
195
- var normalizeOutput = (data) => data.reduce((m, d) => {
145
+ var normalizeOutput = (data) => data.flatMap((d) => {
196
146
  const pid = (d.PID || d.ProcessId)?.[0];
197
147
  const ppid = (d.PPID || d.ParentProcessId)?.[0];
198
- const _cmd = d.CMD || d.CommandLine || d.COMMAND || d.ARGS || [];
199
- const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
200
- if (pid && cmd.length > 0) {
201
- const c = cmd.findIndex((_v, i) => isBin(cmd.slice(0, i).join(" ")));
202
- const command = (c === -1 ? cmd : cmd.slice(0, c)).join(" ");
203
- const args = c === -1 ? [] : cmd.slice(c);
204
- m.push({
205
- pid,
206
- ppid,
207
- command,
208
- arguments: args
209
- });
148
+ const rawCmd = d.CMD || d.CommandLine || d.COMMAND || d.ARGS || [];
149
+ const parts = rawCmd.length === 1 ? rawCmd[0].split(/\s+/) : rawCmd;
150
+ if (!pid || parts.length === 0) return [];
151
+ const binIdx = parts.findIndex((_v, i) => isBin(parts.slice(0, i).join(" ")));
152
+ const command = (binIdx === -1 ? parts : parts.slice(0, binIdx)).join(" ");
153
+ const args = binIdx === -1 ? [] : parts.slice(binIdx);
154
+ return [{ pid, ppid, command, arguments: args }];
155
+ });
156
+ var filterProcessList = (processList, query = {}) => {
157
+ const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map(String);
158
+ const commandRe = query.command ? new RegExp(query.command, "i") : null;
159
+ const argumentsRe = query.arguments ? new RegExp(query.arguments, "i") : null;
160
+ const ppid = query.ppid === void 0 ? null : String(query.ppid);
161
+ return processList.filter(
162
+ (p) => (pidList.length === 0 || pidList.includes(p.pid)) && (!commandRe || commandRe.test(p.command)) && (!argumentsRe || argumentsRe.test(p.arguments.join(" "))) && (!ppid || ppid === p.ppid)
163
+ );
164
+ };
165
+ var removeWmicPrefix = (stdout) => {
166
+ const s = stdout.indexOf(LOOKUPS.wmic.cmd + os.EOL);
167
+ const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(os.EOL) : stdout.length;
168
+ return (s > 0 ? stdout.slice(s + LOOKUPS.wmic.cmd.length, e) : stdout.slice(0, e)).trimStart();
169
+ };
170
+ var isBin = (f) => {
171
+ if (f === "") return false;
172
+ if (!f.includes("/") && !f.includes("\\")) return true;
173
+ if (f.length > 3 && f[0] === '"')
174
+ return f.at(-1) === '"' ? isBin(f.slice(1, -1)) : false;
175
+ try {
176
+ if (!fs.existsSync(f)) return false;
177
+ const stat = fs.lstatSync(f);
178
+ return stat.isFile() || stat.isSymbolicLink();
179
+ } catch {
180
+ return false;
210
181
  }
211
- return m;
212
- }, []);
182
+ };
213
183
  var makeDeferred = () => {
214
184
  let resolve;
215
185
  let reject;
@@ -219,16 +189,16 @@ var makeDeferred = () => {
219
189
  });
220
190
  return { resolve, reject, promise };
221
191
  };
222
- var makePseudoDeferred = (r = {}) => ({
223
- promise: r,
224
- resolve: identity,
192
+ var makeSyncDeferred = (result) => ({
193
+ promise: result,
194
+ resolve: () => {
195
+ },
225
196
  reject(e) {
226
197
  throw e;
227
198
  }
228
199
  });
229
200
  var noop = () => {
230
201
  };
231
- var identity = (v) => v;
232
202
 
233
203
  // src/main/ts/index.ts
234
204
  var index_default = { kill, lookup, lookupSync, tree, treeSync };