@webpod/ps 0.1.4 → 1.1.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.4",
3
+ "version": "1.1.0",
4
4
  "description": "A process lookup utility",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -23,8 +23,8 @@
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:*'",
27
- "test:lint": "eslint -c src/test/lint/.eslintrc.json src",
26
+ "test": "concurrently 'npm:test:lint' 'npm:test:unit' && npm run test:legacy",
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
30
  "publish:draft": "npm run build && npm publish --no-git-tag-version"
@@ -42,24 +42,23 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "@webpod/ingrid": "^1.1.1",
45
- "zurk": "^0.11.4"
45
+ "zurk": "^0.11.10"
46
46
  },
47
47
  "devDependencies": {
48
- "@types/node": "^24.0.13",
49
- "c8": "^10.1.3",
50
- "concurrently": "^9.2.0",
51
- "esbuild": "^0.25.6",
52
- "esbuild-node-externals": "^1.18.0",
53
- "esbuild-plugin-entry-chunks": "^0.1.15",
54
- "eslint": "^8.57.0",
55
- "eslint-config-qiwi": "^2.1.3",
48
+ "@types/node": "^25.5.2",
49
+ "c8": "^11.0.0",
50
+ "concurrently": "^9.2.1",
51
+ "esbuild": "^0.28.0",
52
+ "esbuild-node-externals": "^1.21.0",
53
+ "esbuild-plugin-entry-chunks": "^0.1.18",
54
+ "oxlint": "^1.58.0",
56
55
  "fast-glob": "^3.3.3",
57
56
  "minimist": "^1.2.8",
58
57
  "mocha": "^10.8.2",
59
58
  "sinon": "^18.0.1",
60
59
  "ts-node": "^10.9.2",
61
- "typedoc": "^0.28.7",
62
- "typescript": "^5.8.3"
60
+ "typedoc": "^0.28.18",
61
+ "typescript": "^5.9.2"
63
62
  },
64
63
  "repository": {
65
64
  "type": "git",
@@ -62,11 +62,44 @@ 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: ["-eo", "pid,ppid,args"],
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
105
  if (!f.includes("/") && !f.includes("\\")) return true;
@@ -90,42 +123,31 @@ var _lookup = ({
90
123
  }) => {
91
124
  const pFactory = sync ? makePseudoDeferred.bind(null, []) : makeDeferred;
92
125
  const { promise, resolve, reject } = pFactory();
93
- const { psargs = ["-lx"] } = query;
94
- const args = Array.isArray(psargs) ? psargs : psargs.split(/\s+/);
95
126
  const result = [];
96
- const extract = IS_WIN ? removeWmicPrefix : identity;
127
+ const lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
128
+ const { parse: parse2, cmd, args } = LOOKUPS[lookupFlow];
97
129
  const callback = (err, { stdout }) => {
98
130
  if (err) {
99
131
  reject(err);
100
132
  cb(err);
101
133
  return;
102
134
  }
103
- result.push(...parseProcessList(extract(stdout), query));
135
+ result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
104
136
  resolve(result);
105
137
  cb(null, result);
106
138
  };
107
- const ctx = IS_WIN ? {
108
- cmd: WMIC_INPUT,
109
- args: [],
110
- callback,
111
- sync,
112
- run(cb2) {
113
- cb2();
114
- }
115
- } : {
116
- cmd: "ps",
139
+ (0, import_spawn.exec)({
140
+ cmd,
117
141
  args,
118
142
  callback,
119
143
  sync,
120
144
  run(cb2) {
121
145
  cb2();
122
146
  }
123
- };
124
- (0, import_spawn.exec)(ctx);
147
+ });
125
148
  return Object.assign(promise, result);
126
149
  };
127
- var parseProcessList = (output, query = {}) => {
128
- const processList = parseGrid(output);
150
+ var filterProcessList = (processList, query = {}) => {
129
151
  const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
130
152
  const filters = [
131
153
  (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
@@ -137,9 +159,9 @@ var parseProcessList = (output, query = {}) => {
137
159
  );
138
160
  };
139
161
  var removeWmicPrefix = (stdout) => {
140
- const s = stdout.indexOf(WMIC_INPUT + import_node_os.EOL);
141
- const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(import_node_os.EOL) : stdout.length;
142
- return (s > 0 ? stdout.slice(s + WMIC_INPUT.length, e) : stdout.slice(0, e)).trimStart();
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();
143
165
  };
144
166
  var pickTree = (list, pid, recursive = false) => {
145
167
  const children = list.filter((p) => p.ppid === pid + "");
@@ -233,12 +255,11 @@ var kill = (pid, opts, next) => {
233
255
  }
234
256
  return promise;
235
257
  };
236
- var parseGrid = (output) => output ? formatOutput((0, import_ingrid.parse)(output, { format: IS_WIN ? "win" : "unix" })) : [];
237
- var formatOutput = (data) => data.reduce((m, d) => {
238
- var _a, _b, _c, _d;
239
- const pid = ((_a = d.PID) == null ? void 0 : _a[0]) || ((_b = d.ProcessId) == null ? void 0 : _b[0]);
240
- const ppid = ((_c = d.PPID) == null ? void 0 : _c[0]) || ((_d = d.ParentProcessId) == null ? void 0 : _d[0]);
241
- const _cmd = d.CMD || d.CommandLine || d.COMMAND || [];
258
+ var normalizeOutput = (data) => data.reduce((m, d) => {
259
+ var _a, _b;
260
+ const pid = (_a = d.PID || d.ProcessId) == null ? void 0 : _a[0];
261
+ const ppid = (_b = d.PPID || d.ParentProcessId) == null ? void 0 : _b[0];
262
+ const _cmd = d.CMD || d.CommandLine || d.COMMAND || d.ARGS || [];
242
263
  const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
243
264
  if (pid && cmd.length > 0) {
244
265
  const c = cmd.findIndex((_v, i) => isBin(cmd.slice(0, i).join(" ")));
@@ -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,11 +1,44 @@
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: ["-eo", "pid,ppid,args"],
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
44
  if (!f.includes("/") && !f.includes("\\")) return true;
@@ -29,42 +62,31 @@ var _lookup = ({
29
62
  }) => {
30
63
  const pFactory = sync ? makePseudoDeferred.bind(null, []) : makeDeferred;
31
64
  const { promise, resolve, reject } = pFactory();
32
- const { psargs = ["-lx"] } = query;
33
- const args = Array.isArray(psargs) ? psargs : psargs.split(/\s+/);
34
65
  const result = [];
35
- const extract = IS_WIN ? removeWmicPrefix : identity;
66
+ const lookupFlow = IS_WIN ? IS_WIN2025_PLUS ? "pwsh" : "wmic" : "ps";
67
+ const { parse: parse2, cmd, args } = LOOKUPS[lookupFlow];
36
68
  const callback = (err, { stdout }) => {
37
69
  if (err) {
38
70
  reject(err);
39
71
  cb(err);
40
72
  return;
41
73
  }
42
- result.push(...parseProcessList(extract(stdout), query));
74
+ result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
43
75
  resolve(result);
44
76
  cb(null, result);
45
77
  };
46
- const ctx = IS_WIN ? {
47
- cmd: WMIC_INPUT,
48
- args: [],
49
- callback,
50
- sync,
51
- run(cb2) {
52
- cb2();
53
- }
54
- } : {
55
- cmd: "ps",
78
+ exec({
79
+ cmd,
56
80
  args,
57
81
  callback,
58
82
  sync,
59
83
  run(cb2) {
60
84
  cb2();
61
85
  }
62
- };
63
- exec(ctx);
86
+ });
64
87
  return Object.assign(promise, result);
65
88
  };
66
- var parseProcessList = (output, query = {}) => {
67
- const processList = parseGrid(output);
89
+ var filterProcessList = (processList, query = {}) => {
68
90
  const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
69
91
  const filters = [
70
92
  (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
@@ -76,9 +98,9 @@ var parseProcessList = (output, query = {}) => {
76
98
  );
77
99
  };
78
100
  var removeWmicPrefix = (stdout) => {
79
- const s = stdout.indexOf(WMIC_INPUT + SystemEOL);
80
- const e = stdout.includes(">") ? stdout.trimEnd().lastIndexOf(SystemEOL) : stdout.length;
81
- return (s > 0 ? stdout.slice(s + WMIC_INPUT.length, e) : stdout.slice(0, e)).trimStart();
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();
82
104
  };
83
105
  var pickTree = (list, pid, recursive = false) => {
84
106
  const children = list.filter((p) => p.ppid === pid + "");
@@ -170,11 +192,10 @@ var kill = (pid, opts, next) => {
170
192
  }
171
193
  return promise;
172
194
  };
173
- var parseGrid = (output) => output ? formatOutput(parse(output, { format: IS_WIN ? "win" : "unix" })) : [];
174
- var formatOutput = (data) => data.reduce((m, d) => {
175
- const pid = d.PID?.[0] || d.ProcessId?.[0];
176
- const ppid = d.PPID?.[0] || d.ParentProcessId?.[0];
177
- const _cmd = d.CMD || d.CommandLine || d.COMMAND || [];
195
+ var normalizeOutput = (data) => data.reduce((m, d) => {
196
+ const pid = (d.PID || d.ProcessId)?.[0];
197
+ const ppid = (d.PPID || d.ParentProcessId)?.[0];
198
+ const _cmd = d.CMD || d.CommandLine || d.COMMAND || d.ARGS || [];
178
199
  const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
179
200
  if (pid && cmd.length > 0) {
180
201
  const c = cmd.findIndex((_v, i) => isBin(cmd.slice(0, i).join(" ")));