Skip to content

Commit 871d44d

Browse files
committed
feat: add tree.sync and lookup.sync
1 parent 7e25163 commit 871d44d

File tree

4 files changed

+135
-23
lines changed

4 files changed

+135
-23
lines changed

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* [x] CJS and ESM package entry points
88
* [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)
99
* [x] Provides promisified responses
10-
* [ ] Brings sync API
10+
* [x] Brings sync API
1111
* [x] Builds a process tree
1212

1313
## Install
@@ -44,6 +44,9 @@ lookup({pid: 12345}, (err, list) => {
4444
console.log('No such process found!')
4545
}
4646
})
47+
48+
// or syncronously
49+
const _list = lookup.sync({pid: 12345})
4750
```
4851

4952
Define a query opts to filter the results by `command` and/or `arguments` predicates:
@@ -109,6 +112,9 @@ const children = await tree({pid: 123, recursive: true})
109112
{pid: 130, ppid: 125},
110113
]
111114
*/
115+
116+
// or syncronously
117+
const list = tree.sync({pid: 123, recursive: true})
112118
```
113119

114120
### kill()

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/ts/ps.ts

+80-19
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import process from 'node:process'
22
import fs from 'node:fs'
3+
import { EOL as SystemEOL } from 'node:os'
34
import { parse, TIngridResponse } from '@webpod/ingrid'
45
import { exec, TSpawnCtx } from 'zurk/spawn'
5-
import { EOL as SystemEOL } from 'node:os'
66

77
const EOL = /(\r\n)|(\n\r)|\n|\r/
88
const IS_WIN = process.platform === 'win32'
@@ -46,44 +46,76 @@ export type TPsNext = (err?: any, data?: any) => void
4646
* @param {String} query.command RegExp String
4747
* @param {String} query.arguments RegExp String
4848
* @param {String|String[]} query.psargs
49-
* @param {Function} cb
49+
* @param {Function} [cb]
50+
* @param {Object=null} cb.err
51+
* @param {TPsLookupEntry[]} cb.processList
52+
* @return {Promise<TPsLookupEntry[]>}
53+
*/
54+
export const lookup = (query: TPsLookupQuery = {}, cb: TPsLookupCallback = noop): Promise<TPsLookupEntry[]> =>
55+
_lookup({query, cb, sync: false})
56+
57+
/**
58+
* Looks up the process list synchronously
59+
* @param query
60+
* @param {String|String[]} query.pid
61+
* @param {String} query.command RegExp String
62+
* @param {String} query.arguments RegExp String
63+
* @param {String|String[]} query.psargs
64+
* @param {Function} [cb]
5065
* @param {Object=null} cb.err
5166
* @param {Object[]} cb.processList
52-
* @return {Object}
67+
* @return {TPsLookupEntry[]}
5368
*/
54-
export const lookup = (query: TPsLookupQuery = {}, cb: TPsLookupCallback = noop) => {
55-
const { promise, resolve, reject } = makeDeferred<TPsLookupEntry[]>()
69+
export const lookupSync = (query: TPsLookupQuery = {}, cb: TPsLookupCallback = noop): TPsLookupEntry[] =>
70+
_lookup({query, cb, sync: true})
71+
72+
lookup.sync = lookupSync
73+
74+
const _lookup = ({
75+
query = {},
76+
cb = noop,
77+
sync = false
78+
}: {
79+
sync?: boolean
80+
cb?: TPsLookupCallback
81+
query?: TPsLookupQuery
82+
}) => {
83+
const pFactory = sync ? makePseudoDeferred.bind(null, []) : makeDeferred
84+
const { promise, resolve, reject } = pFactory()
5685
const { psargs = ['-lx'] } = query // add 'lx' as default ps arguments, since the default ps output in linux like "ubuntu", wont include command arguments
57-
const args = typeof psargs === 'string' ? psargs.split(/\s+/) : psargs
86+
const args = Array.isArray(psargs) ? psargs : psargs.split(/\s+/)
5887
const extract = IS_WIN ? extractWmic : identity
88+
89+
let result: TPsLookupEntry[] = []
5990
const callback: TSpawnCtx['callback'] = (err, {stdout}) => {
6091
if (err) {
6192
reject(err)
6293
cb(err)
6394
return
6495
}
65-
66-
const list = parseProcessList(extract(stdout), query)
67-
resolve(list)
68-
cb(null, list)
96+
result = parseProcessList(extract(stdout), query)
97+
resolve(result)
98+
cb(null, result)
6999
}
70100
const ctx: TSpawnCtx = IS_WIN
71101
? {
72102
cmd: 'cmd',
73103
input: 'wmic process get ProcessId,ParentProcessId,CommandLine \n',
74104
callback,
105+
sync,
75106
run(cb) {cb()}
76107
}
77108
: {
78109
cmd: 'ps',
79110
args,
80111
run(cb) {cb()},
112+
sync,
81113
callback,
82114
}
83115

84116
exec(ctx)
85117

86-
return promise
118+
return Object.assign(promise, result)
87119
}
88120

89121
export const parseProcessList = (output: string, query: TPsLookupQuery = {}) => {
@@ -128,26 +160,45 @@ export const pickTree = (list: TPsLookupEntry[], pid: string | number, recursive
128160
]
129161
}
130162

131-
export const tree = async (opts?: string | number | TPsTreeOpts | undefined, cb: TPsLookupCallback = noop): Promise<TPsLookupEntry[]> => {
163+
const _tree = ({
164+
cb = noop,
165+
opts,
166+
sync = false
167+
}: {
168+
opts?: string | number | TPsTreeOpts | undefined
169+
cb?: TPsLookupCallback
170+
sync?: boolean
171+
}): any => {
132172
if (typeof opts === 'string' || typeof opts === 'number') {
133-
return tree({ pid: opts }, cb)
173+
return _tree({opts: {pid: opts}, cb, sync})
134174
}
135-
136-
try {
137-
const all = await lookup()
175+
const handle = (all: TPsLookupEntry[]) => {
138176
if (opts === undefined) return all
139177

140178
const {pid, recursive = false} = opts
141179
const list = pickTree(all, pid, recursive)
142180

143181
cb(null, list)
144182
return list
183+
}
184+
185+
try {
186+
const all = _lookup({sync})
187+
return sync ? handle(all) : all.then(handle)
145188
} catch (err) {
146189
cb(err)
147190
throw err
148191
}
149192
}
150193

194+
export const tree = async (opts?: string | number | TPsTreeOpts | undefined, cb?: TPsLookupCallback): Promise<TPsLookupEntry[]> =>
195+
_tree({opts, cb})
196+
197+
export const treeSync = (opts?: string | number | TPsTreeOpts | undefined, cb?: TPsLookupCallback): TPsLookupEntry[] =>
198+
_tree({opts, cb, sync: true})
199+
200+
tree.sync = treeSync
201+
151202
/**
152203
* Kill process
153204
* @param pid
@@ -253,13 +304,23 @@ export const formatOutput = (data: TIngridResponse): TPsLookupEntry[] =>
253304

254305
export type PromiseResolve<T = any> = (value?: T | PromiseLike<T>) => void
255306

256-
export const makeDeferred = <T = any, E = any>(): { promise: Promise<T>, resolve: PromiseResolve<T>, reject: PromiseResolve<E> } => {
307+
const makeDeferred = <T = any, E = any>(): { promise: Promise<T>, resolve: PromiseResolve<T>, reject: PromiseResolve<E> } => {
257308
let resolve
258309
let reject
259310
const promise = new Promise<T>((res, rej) => { resolve = res; reject = rej })
260311
return { resolve, reject, promise } as any
261312
}
262313

263-
export const noop = () => {/* noop */}
314+
const makePseudoDeferred = <T = any, E = any>(r = {}): { promise: any, resolve: any, reject: any } => {
315+
return {
316+
promise: r as any,
317+
resolve: identity,
318+
reject(e: any) {
319+
throw e
320+
}
321+
}
322+
}
323+
324+
const noop = () => {/* noop */}
264325

265-
export const identity = <T>(v: T): T => v
326+
const identity = <T>(v: T): T => v

src/test/ts/ps.test.ts

+46-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, it, before, after } from 'node:test'
33
import process from 'node:process'
44
import * as cp from 'node:child_process'
55
import * as path from 'node:path'
6-
import { kill, lookup, tree } from '../../main/ts/ps.ts'
6+
import { kill, lookup, lookupSync, tree, treeSync } from '../../main/ts/ps.ts'
77

88
const __dirname = new URL('.', import.meta.url).pathname
99
const marker = Math.random().toString(16).slice(2)
@@ -43,6 +43,27 @@ describe('lookup()', () => {
4343
})
4444
})
4545

46+
describe('lookupSync()', () => {
47+
let pid: number
48+
before(() => {
49+
pid = cp.fork(testScript, testScriptArgs).pid as number
50+
})
51+
52+
after(() => {
53+
try {
54+
process.kill(pid)
55+
} catch (err) { void err }
56+
})
57+
58+
it('returns a process list', () => {
59+
const list = lookupSync()
60+
assert.ok(list.length > 0)
61+
})
62+
it('lookup.sync refs to lookupSync', () => {
63+
assert.equal(lookup.sync, lookupSync)
64+
})
65+
})
66+
4667
describe('kill()', () => {
4768
it('kills a process', async () => {
4869
const pid = cp.fork(testScript, testScriptArgs).pid as number
@@ -89,3 +110,27 @@ describe('tree()', () => {
89110
assert.ok(list.length > 0)
90111
})
91112
})
113+
114+
describe('treeSync()', () => {
115+
it('tree.sync refs to treeSync', () => {
116+
assert.equal(tree.sync, treeSync)
117+
})
118+
119+
it('returns 1st level child', async () => {
120+
const pid = cp.fork(testScript, [...testScriptArgs, '--fork=1', '--depth=2']).pid as number
121+
await new Promise(resolve => setTimeout(resolve, 2000)) // wait for child process to spawn
122+
123+
const list = lookupSync({ arguments: marker })
124+
const children = treeSync(pid)
125+
const childrenAll = treeSync({pid, recursive: true})
126+
127+
await Promise.all(list.map(p => kill(p.pid)))
128+
await kill(pid)
129+
130+
assert.equal(children.length, 1)
131+
assert.equal(childrenAll.length, 2)
132+
assert.equal(list.length, 3)
133+
134+
assert.equal((await lookup({ arguments: marker })).length, 0)
135+
})
136+
})

0 commit comments

Comments
 (0)