@webpod/ps 0.1.4 → 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.4",
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,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: ["-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
105
  if (!f.includes("/") && !f.includes("\\")) return true;
@@ -90,42 +123,35 @@ 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 {
129
+ parse: parse2,
130
+ cmd,
131
+ args
132
+ } = LOOKUPS[lookupFlow];
97
133
  const callback = (err, { stdout }) => {
98
134
  if (err) {
99
135
  reject(err);
100
136
  cb(err);
101
137
  return;
102
138
  }
103
- result.push(...parseProcessList(extract(stdout), query));
139
+ result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
104
140
  resolve(result);
105
141
  cb(null, result);
106
142
  };
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",
143
+ (0, import_spawn.exec)({
144
+ cmd,
117
145
  args,
118
146
  callback,
119
147
  sync,
120
148
  run(cb2) {
121
149
  cb2();
122
150
  }
123
- };
124
- (0, import_spawn.exec)(ctx);
151
+ });
125
152
  return Object.assign(promise, result);
126
153
  };
127
- var parseProcessList = (output, query = {}) => {
128
- const processList = parseGrid(output);
154
+ var filterProcessList = (processList, query = {}) => {
129
155
  const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
130
156
  const filters = [
131
157
  (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
@@ -137,9 +163,9 @@ var parseProcessList = (output, query = {}) => {
137
163
  );
138
164
  };
139
165
  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();
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();
143
169
  };
144
170
  var pickTree = (list, pid, recursive = false) => {
145
171
  const children = list.filter((p) => p.ppid === pid + "");
@@ -233,11 +259,10 @@ var kill = (pid, opts, next) => {
233
259
  }
234
260
  return promise;
235
261
  };
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]);
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];
241
266
  const _cmd = d.CMD || d.CommandLine || d.COMMAND || [];
242
267
  const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
243
268
  if (pid && cmd.length > 0) {
@@ -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: ["-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
44
  if (!f.includes("/") && !f.includes("\\")) return true;
@@ -29,42 +62,35 @@ 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 {
68
+ parse: parse2,
69
+ cmd,
70
+ args
71
+ } = LOOKUPS[lookupFlow];
36
72
  const callback = (err, { stdout }) => {
37
73
  if (err) {
38
74
  reject(err);
39
75
  cb(err);
40
76
  return;
41
77
  }
42
- result.push(...parseProcessList(extract(stdout), query));
78
+ result.push(...filterProcessList(normalizeOutput(parse2(stdout)), query));
43
79
  resolve(result);
44
80
  cb(null, result);
45
81
  };
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",
82
+ exec({
83
+ cmd,
56
84
  args,
57
85
  callback,
58
86
  sync,
59
87
  run(cb2) {
60
88
  cb2();
61
89
  }
62
- };
63
- exec(ctx);
90
+ });
64
91
  return Object.assign(promise, result);
65
92
  };
66
- var parseProcessList = (output, query = {}) => {
67
- const processList = parseGrid(output);
93
+ var filterProcessList = (processList, query = {}) => {
68
94
  const pidList = (query.pid === void 0 ? [] : [query.pid].flat(1)).map((v) => v + "");
69
95
  const filters = [
70
96
  (p) => query.command ? new RegExp(query.command, "i").test(p.command) : true,
@@ -76,9 +102,9 @@ var parseProcessList = (output, query = {}) => {
76
102
  );
77
103
  };
78
104
  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();
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();
82
108
  };
83
109
  var pickTree = (list, pid, recursive = false) => {
84
110
  const children = list.filter((p) => p.ppid === pid + "");
@@ -170,10 +196,9 @@ var kill = (pid, opts, next) => {
170
196
  }
171
197
  return promise;
172
198
  };
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];
199
+ var normalizeOutput = (data) => data.reduce((m, d) => {
200
+ const pid = (d.PID || d.ProcessId)?.[0];
201
+ const ppid = (d.PPID || d.ParentProcessId)?.[0];
177
202
  const _cmd = d.CMD || d.CommandLine || d.COMMAND || [];
178
203
  const cmd = _cmd.length === 1 ? _cmd[0].split(/\s+/) : _cmd;
179
204
  if (pid && cmd.length > 0) {