@webpod/ps 0.1.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
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
9
  * [x] Provides promisified responses
10
10
  * [x] Brings sync API
11
- * [x] Builds a process tree
11
+ * [x] Builds a process subtree by parent
12
12
 
13
13
  ## Install
14
14
  ```bash
@@ -16,10 +16,13 @@ $ npm install @webpod/ps
16
16
  ```
17
17
 
18
18
  ## Internals
19
- This module invokes different tools to get process list:
19
+ This module uses different approaches for getting process list:
20
20
 
21
- * `ps` for unix/mac: `ps -lx`
22
- * [`wmic` for win runtimes](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmic): `wmic process get ProcessId,CommandLine`.
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` |
23
26
 
24
27
  ## Usage
25
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpod/ps",
3
- "version": "0.1.3",
3
+ "version": "1.0.0",
4
4
  "description": "A process lookup utility",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,10 +23,10 @@
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:*'",
26
+ "test": "concurrently 'npm:test:*' && npm run x:test:legacy",
27
27
  "test:lint": "eslint -c src/test/lint/.eslintrc.json src",
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
- "test:legacy": "node ./node_modules/mocha/bin/mocha -t 0 -R spec src/test/legacy/test.cjs",
29
+ "x:test:legacy": "node ./node_modules/mocha/bin/mocha -t 0 -R spec src/test/legacy/test.cjs",
30
30
  "publish:draft": "npm run build && npm publish --no-git-tag-version"
31
31
  },
32
32
  "files": [
@@ -42,15 +42,15 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "@webpod/ingrid": "^1.1.1",
45
- "zurk": "^0.11.4"
45
+ "zurk": "^0.11.5"
46
46
  },
47
47
  "devDependencies": {
48
- "@types/node": "^24.0.13",
48
+ "@types/node": "^24.5.2",
49
49
  "c8": "^10.1.3",
50
- "concurrently": "^9.2.0",
51
- "esbuild": "^0.25.6",
50
+ "concurrently": "^9.2.1",
51
+ "esbuild": "^0.25.10",
52
52
  "esbuild-node-externals": "^1.18.0",
53
- "esbuild-plugin-entry-chunks": "^0.1.15",
53
+ "esbuild-plugin-entry-chunks": "^0.1.17",
54
54
  "eslint": "^8.57.0",
55
55
  "eslint-config-qiwi": "^2.1.3",
56
56
  "fast-glob": "^3.3.3",
@@ -58,8 +58,8 @@
58
58
  "mocha": "^10.8.2",
59
59
  "sinon": "^18.0.1",
60
60
  "ts-node": "^10.9.2",
61
- "typedoc": "^0.28.7",
62
- "typescript": "^5.8.3"
61
+ "typedoc": "^0.28.13",
62
+ "typescript": "^5.9.2"
63
63
  },
64
64
  "repository": {
65
65
  "type": "git",
@@ -62,20 +62,56 @@ module.exports = __toCommonJS(index_exports);
62
62
  // src/main/ts/ps.ts
63
63
  var import_node_process = __toESM(require("node:process"), 1);
64
64
  var import_node_fs = __toESM(require("node:fs"), 1);
65
- var import_node_os = require("node:os");
65
+ var import_node_os = __toESM(require("node:os"), 1);
66
66
  var import_ingrid = require("@webpod/ingrid");
67
67
  var import_spawn = require("zurk/spawn");
68
68
  var IS_WIN = import_node_process.default.platform === "win32";
69
- var WMIC_INPUT = "wmic process get ProcessId,ParentProcessId,CommandLine";
69
+ var IS_WIN2025_PLUS = IS_WIN && Number.parseInt(import_node_os.default.release().split(".")[2], 10) >= 26e3;
70
+ var LOOKUPS = {
71
+ wmic: {
72
+ cmd: "wmic process get ProcessId,ParentProcessId,CommandLine",
73
+ args: [],
74
+ parse(stdout) {
75
+ return (0, import_ingrid.parse)(removeWmicPrefix(stdout), { format: "win" });
76
+ }
77
+ },
78
+ ps: {
79
+ cmd: "ps",
80
+ args: ["-lx"],
81
+ parse(stdout) {
82
+ return (0, import_ingrid.parse)(stdout, { format: "unix" });
83
+ }
84
+ },
85
+ pwsh: {
86
+ cmd: "pwsh",
87
+ args: ["-NoProfile", "-Command", '"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine | ConvertTo-Json -Compress"'],
88
+ parse(stdout) {
89
+ let arr = [];
90
+ try {
91
+ arr = JSON.parse(stdout);
92
+ } catch (e) {
93
+ return [];
94
+ }
95
+ return arr.map((p) => ({
96
+ ProcessId: [p.ProcessId + ""],
97
+ ParentProcessId: [p.ParentProcessId + ""],
98
+ CommandLine: p.CommandLine ? [p.CommandLine] : []
99
+ }));
100
+ }
101
+ }
102
+ };
70
103
  var isBin = (f) => {
71
104
  if (f === "") return false;
72
- if (!f.includes("/")) return true;
73
- if (!f.includes("\\")) return true;
105
+ if (!f.includes("/") && !f.includes("\\")) return true;
74
106
  if (f.length > 3 && f[0] === '"')
75
107
  return f[f.length - 1] === '"' ? isBin(f.slice(1, -1)) : false;
76
- if (!import_node_fs.default.existsSync(f)) return false;
77
- const stat = import_node_fs.default.lstatSync(f);
78
- return stat.isFile() || stat.isSymbolicLink();
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
+ }
79
115
  };
80
116
  var lookup = (query = {}, cb = noop) => _lookup({ query, cb, sync: false });
81
117
  var lookupSync = (query = {}, cb = noop) => _lookup({ query, cb, sync: true });
@@ -87,42 +123,35 @@ var _lookup = ({
87
123
  }) => {
88
124
  const pFactory = sync ? makePseudoDeferred.bind(null, []) : makeDeferred;
89
125
  const { promise, resolve, reject } = pFactory();
90
- const { psargs = ["-lx"] } = query;
91
- const args = Array.isArray(psargs) ? psargs : psargs.split(/\s+/);
92
126
  const result = [];
93
- const extract = IS_WIN ? removeWmicPrefix : identity;
127
+ const lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
128
+ const {
129
+ parse: parse2,
130
+ cmd,
131
+ args
132
+ } = LOOKUPS[lookupFlow];
94
133
  const callback = (err, { stdout }) => {
95
134
  if (err) {
96
135
  reject(err);
97
136
  cb(err);
98
137
  return;
99
138
  }
100
- result.push(...parseProcessList(extract(stdout), query));
139
+ result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
101
140
  resolve(result);
102
141
  cb(null, result);
103
142
  };
104
- const ctx = IS_WIN ? {
105
- cmd: WMIC_INPUT,
106
- args: [],
107
- callback,
108
- sync,
109
- run(cb2) {
110
- cb2();
111
- }
112
- } : {
113
- cmd: "ps",
143
+ (0, import_spawn.exec)({
144
+ cmd,
114
145
  args,
115
146
  callback,
116
147
  sync,
117
148
  run(cb2) {
118
149
  cb2();
119
150
  }
120
- };
121
- (0, import_spawn.exec)(ctx);
151
+ });
122
152
  return Object.assign(promise, result);
123
153
  };
124
- var parseProcessList = (output, query = {}) => {
125
- const processList = parseGrid(output);
154
+ var filterProcessList = (processList, query = {}) => {
126
155
  const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
127
156
  const filters = [
128
157
  (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
@@ -134,9 +163,9 @@ var parseProcessList = (output, query = {}) => {
134
163
  );
135
164
  };
136
165
  var removeWmicPrefix = (stdout) => {
137
- const s = stdout.indexOf(WMIC_INPUT + import_node_os.EOL);
138
- const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(import_node_os.EOL) : stdout.length;
139
- return (s > 0 ? stdout.slice(s + WMIC_INPUT.length, e) : stdout.slice(0, e)).trimStart();
166
+ const s = stdout.indexOf(LOOKUPS.wmic.cmd + import_node_os.default.EOL);
167
+ const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(import_node_os.default.EOL) : stdout.length;
168
+ return (s > 0 ? stdout.slice(s + LOOKUPS.wmic.cmd.length, e) : stdout.slice(0, e)).trimStart();
140
169
  };
141
170
  var pickTree = (list, pid, recursive = false) => {
142
171
  const children = list.filter((p) => p.ppid === pid + "");
@@ -230,17 +259,16 @@ var kill = (pid, opts, next) => {
230
259
  }
231
260
  return promise;
232
261
  };
233
- var parseGrid = (output) => output ? formatOutput((0, import_ingrid.parse)(output, { format: IS_WIN ? "win" : "unix" })) : [];
234
- var formatOutput = (data) => data.reduce((m, d) => {
235
- var _a, _b, _c, _d;
236
- const pid = ((_a = d.PID) == null ? void 0 : _a[0]) || ((_b = d.ProcessId) == null ? void 0 : _b[0]);
237
- const ppid = ((_c = d.PPID) == null ? void 0 : _c[0]) || ((_d = d.ParentProcessId) == null ? void 0 : _d[0]);
262
+ var normalizeOutput = (data) => data.reduce((m, d) => {
263
+ var _a, _b;
264
+ const pid = (_a = d.PID || d.ProcessId) == null ? void 0 : _a[0];
265
+ const ppid = (_b = d.PPID || d.ParentProcessId) == null ? void 0 : _b[0];
238
266
  const _cmd = d.CMD || d.CommandLine || d.COMMAND || [];
239
267
  const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
240
268
  if (pid && cmd.length > 0) {
241
269
  const c = cmd.findIndex((_v, i) => isBin(cmd.slice(0, i).join(" ")));
242
- const command = cmd.slice(0, c).join(" ");
243
- const args = cmd.length > 1 ? cmd.slice(c) : [];
270
+ const command = (c === -1 ? cmd : cmd.slice(0, c)).join(" ");
271
+ const args = c === -1 ? [] : cmd.slice(c);
244
272
  m.push({
245
273
  pid,
246
274
  ppid,
@@ -1,4 +1,4 @@
1
- import { TIngridResponse } from '@webpod/ingrid';
1
+ import { type TIngridResponse } from '@webpod/ingrid';
2
2
  export type TPsLookupCallback = (err: any, processList?: TPsLookupEntry[]) => void;
3
3
  export type TPsLookupEntry = {
4
4
  pid: string;
@@ -11,7 +11,6 @@ export type TPsLookupQuery = {
11
11
  command?: string;
12
12
  arguments?: string;
13
13
  ppid?: number | string;
14
- psargs?: string | string[];
15
14
  };
16
15
  export type TPsKillOptions = {
17
16
  timeout?: number;
@@ -43,7 +42,7 @@ export declare const lookup: {
43
42
  * @return {TPsLookupEntry[]}
44
43
  */
45
44
  export declare const lookupSync: (query?: TPsLookupQuery, cb?: TPsLookupCallback) => TPsLookupEntry[];
46
- export declare const parseProcessList: (output: string, query?: TPsLookupQuery) => TPsLookupEntry[];
45
+ export declare const filterProcessList: (processList: TPsLookupEntry[], query?: TPsLookupQuery) => TPsLookupEntry[];
47
46
  export declare const removeWmicPrefix: (stdout: string) => string;
48
47
  export type TPsTreeOpts = {
49
48
  pid: string | number;
@@ -64,6 +63,5 @@ export declare const treeSync: (opts?: string | number | TPsTreeOpts | undefined
64
63
  * @param next
65
64
  */
66
65
  export declare const kill: (pid: string | number, opts?: TPsNext | TPsKillOptions | TPsKillOptions["signal"], next?: TPsNext) => Promise<void>;
67
- export declare const parseGrid: (output: string) => TPsLookupEntry[];
68
- export declare const formatOutput: (data: TIngridResponse) => TPsLookupEntry[];
66
+ export declare const normalizeOutput: (data: TIngridResponse) => TPsLookupEntry[];
69
67
  export type PromiseResolve<T = any> = (value?: T | PromiseLike<T>) => void;
@@ -1,20 +1,56 @@
1
1
  // src/main/ts/ps.ts
2
2
  import process from "node:process";
3
3
  import fs from "node:fs";
4
- import { EOL as SystemEOL } from "node:os";
4
+ import os from "node:os";
5
5
  import { parse } from "@webpod/ingrid";
6
6
  import { exec } from "zurk/spawn";
7
7
  var IS_WIN = process.platform === "win32";
8
- var WMIC_INPUT = "wmic process get ProcessId,ParentProcessId,CommandLine";
8
+ var IS_WIN2025_PLUS = IS_WIN && Number.parseInt(os.release().split(".")[2], 10) >= 26e3;
9
+ var LOOKUPS = {
10
+ wmic: {
11
+ cmd: "wmic process get ProcessId,ParentProcessId,CommandLine",
12
+ args: [],
13
+ parse(stdout) {
14
+ return parse(removeWmicPrefix(stdout), { format: "win" });
15
+ }
16
+ },
17
+ ps: {
18
+ cmd: "ps",
19
+ args: ["-lx"],
20
+ parse(stdout) {
21
+ return parse(stdout, { format: "unix" });
22
+ }
23
+ },
24
+ pwsh: {
25
+ cmd: "pwsh",
26
+ args: ["-NoProfile", "-Command", '"Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine | ConvertTo-Json -Compress"'],
27
+ parse(stdout) {
28
+ let arr = [];
29
+ try {
30
+ arr = JSON.parse(stdout);
31
+ } catch {
32
+ return [];
33
+ }
34
+ return arr.map((p) => ({
35
+ ProcessId: [p.ProcessId + ""],
36
+ ParentProcessId: [p.ParentProcessId + ""],
37
+ CommandLine: p.CommandLine ? [p.CommandLine] : []
38
+ }));
39
+ }
40
+ }
41
+ };
9
42
  var isBin = (f) => {
10
43
  if (f === "") return false;
11
- if (!f.includes("/")) return true;
12
- if (!f.includes("\\")) return true;
44
+ if (!f.includes("/") && !f.includes("\\")) return true;
13
45
  if (f.length > 3 && f[0] === '"')
14
46
  return f[f.length - 1] === '"' ? isBin(f.slice(1, -1)) : false;
15
- if (!fs.existsSync(f)) return false;
16
- const stat = fs.lstatSync(f);
17
- return stat.isFile() || stat.isSymbolicLink();
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
+ }
18
54
  };
19
55
  var lookup = (query = {}, cb = noop) => _lookup({ query, cb, sync: false });
20
56
  var lookupSync = (query = {}, cb = noop) => _lookup({ query, cb, sync: true });
@@ -26,42 +62,35 @@ var _lookup = ({
26
62
  }) => {
27
63
  const pFactory = sync ? makePseudoDeferred.bind(null, []) : makeDeferred;
28
64
  const { promise, resolve, reject } = pFactory();
29
- const { psargs = ["-lx"] } = query;
30
- const args = Array.isArray(psargs) ? psargs : psargs.split(/\s+/);
31
65
  const result = [];
32
- const extract = IS_WIN ? removeWmicPrefix : identity;
66
+ const lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
67
+ const {
68
+ parse: parse2,
69
+ cmd,
70
+ args
71
+ } = LOOKUPS[lookupFlow];
33
72
  const callback = (err, { stdout }) => {
34
73
  if (err) {
35
74
  reject(err);
36
75
  cb(err);
37
76
  return;
38
77
  }
39
- result.push(...parseProcessList(extract(stdout), query));
78
+ result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
40
79
  resolve(result);
41
80
  cb(null, result);
42
81
  };
43
- const ctx = IS_WIN ? {
44
- cmd: WMIC_INPUT,
45
- args: [],
46
- callback,
47
- sync,
48
- run(cb2) {
49
- cb2();
50
- }
51
- } : {
52
- cmd: "ps",
82
+ exec({
83
+ cmd,
53
84
  args,
54
85
  callback,
55
86
  sync,
56
87
  run(cb2) {
57
88
  cb2();
58
89
  }
59
- };
60
- exec(ctx);
90
+ });
61
91
  return Object.assign(promise, result);
62
92
  };
63
- var parseProcessList = (output, query = {}) => {
64
- const processList = parseGrid(output);
93
+ var filterProcessList = (processList, query = {}) => {
65
94
  const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
66
95
  const filters = [
67
96
  (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
@@ -73,9 +102,9 @@ var parseProcessList = (output, query = {}) => {
73
102
  );
74
103
  };
75
104
  var removeWmicPrefix = (stdout) => {
76
- const s = stdout.indexOf(WMIC_INPUT + SystemEOL);
77
- const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(SystemEOL) : stdout.length;
78
- return (s > 0 ? stdout.slice(s + WMIC_INPUT.length, e) : stdout.slice(0, e)).trimStart();
105
+ const s = stdout.indexOf(LOOKUPS.wmic.cmd + os.EOL);
106
+ const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(os.EOL) : stdout.length;
107
+ return (s > 0 ? stdout.slice(s + LOOKUPS.wmic.cmd.length, e) : stdout.slice(0, e)).trimStart();
79
108
  };
80
109
  var pickTree = (list, pid, recursive = false) => {
81
110
  const children = list.filter((p) => p.ppid === pid + "");
@@ -167,16 +196,15 @@ var kill = (pid, opts, next) => {
167
196
  }
168
197
  return promise;
169
198
  };
170
- var parseGrid = (output) => output ? formatOutput(parse(output, { format: IS_WIN ? "win" : "unix" })) : [];
171
- var formatOutput = (data) => data.reduce((m, d) => {
172
- const pid = d.PID?.[0] || d.ProcessId?.[0];
173
- const ppid = d.PPID?.[0] || d.ParentProcessId?.[0];
199
+ var normalizeOutput = (data) => data.reduce((m, d) => {
200
+ const pid = (d.PID || d.ProcessId)?.[0];
201
+ const ppid = (d.PPID || d.ParentProcessId)?.[0];
174
202
  const _cmd = d.CMD || d.CommandLine || d.COMMAND || [];
175
203
  const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
176
204
  if (pid && cmd.length > 0) {
177
205
  const c = cmd.findIndex((_v, i) => isBin(cmd.slice(0, i).join(" ")));
178
- const command = cmd.slice(0, c).join(" ");
179
- const args = cmd.length > 1 ? cmd.slice(c) : [];
206
+ const command = (c === -1 ? cmd : cmd.slice(0, c)).join(" ");
207
+ const args = c === -1 ? [] : cmd.slice(c);
180
208
  m.push({
181
209
  pid,
182
210
  ppid,