@qzda/port 0.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 +47 -0
- package/dist/format.js +19 -0
- package/dist/format.js.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/darwin.js +63 -0
- package/dist/parsers/darwin.js.map +1 -0
- package/dist/parsers/linux.js +85 -0
- package/dist/parsers/linux.js.map +1 -0
- package/dist/parsers/win32.js +87 -0
- package/dist/parsers/win32.js.map +1 -0
- package/dist/scanner.js +16 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @qzda/port
|
|
2
|
+
|
|
3
|
+
Cross-platform CLI tool to list listening TCP ports and the programs occupying them.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @qzda/port
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
port list
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Example output:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
PID PORT PROGRAM
|
|
21
|
+
----- ---- -------
|
|
22
|
+
1234 22 sshd
|
|
23
|
+
5678 80 nginx
|
|
24
|
+
12345 3000 node
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Supported Platforms
|
|
28
|
+
|
|
29
|
+
| Platform | Method |
|
|
30
|
+
|----------|--------|
|
|
31
|
+
| Linux | `ss -tlnp` (fallback `netstat -tlnp`) |
|
|
32
|
+
| macOS | `lsof -iTCP -sTCP:LISTEN -P -n` |
|
|
33
|
+
| Windows | `netstat -ano` + `tasklist` |
|
|
34
|
+
|
|
35
|
+
## Development
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
git clone git@github.com:qzda/port.git
|
|
39
|
+
cd port
|
|
40
|
+
npm install
|
|
41
|
+
npm run build
|
|
42
|
+
node dist/index.js list
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatTable = formatTable;
|
|
4
|
+
function formatTable(entries) {
|
|
5
|
+
if (entries.length === 0) {
|
|
6
|
+
return 'No listening ports found.';
|
|
7
|
+
}
|
|
8
|
+
const pidHeader = 'PID';
|
|
9
|
+
const portHeader = 'PORT';
|
|
10
|
+
const programHeader = 'PROGRAM';
|
|
11
|
+
const pidWidth = Math.max(pidHeader.length, ...entries.map((e) => String(e.pid).length));
|
|
12
|
+
const portWidth = Math.max(portHeader.length, ...entries.map((e) => String(e.port).length));
|
|
13
|
+
const programWidth = Math.max(programHeader.length, ...entries.map((e) => e.program.length));
|
|
14
|
+
const header = `${pidHeader.padEnd(pidWidth)} ${portHeader.padEnd(portWidth)} ${programHeader}`;
|
|
15
|
+
const separator = `${'-'.repeat(pidWidth)} ${'-'.repeat(portWidth)} ${'-'.repeat(programWidth)}`;
|
|
16
|
+
const rows = entries.map((e) => `${String(e.pid).padEnd(pidWidth)} ${String(e.port).padEnd(portWidth)} ${e.program}`);
|
|
17
|
+
return [header, separator, ...rows].join('\n');
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":";;AAEA,kCA+BC;AA/BD,SAAgB,WAAW,CAAC,OAAoB;IAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,2BAA2B,CAAC;IACrC,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC;IACxB,MAAM,UAAU,GAAG,MAAM,CAAC;IAC1B,MAAM,aAAa,GAAG,SAAS,CAAC;IAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CACvB,SAAS,CAAC,MAAM,EAChB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAC5C,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CACxB,UAAU,CAAC,MAAM,EACjB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAC7C,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAC3B,aAAa,CAAC,MAAM,EACpB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CACxC,CAAC;IAEF,MAAM,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,aAAa,EAAE,CAAC;IAClG,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;IAEnG,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CACtB,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CACzF,CAAC;IAEF,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const scanner_js_1 = require("./scanner.js");
|
|
6
|
+
const format_js_1 = require("./format.js");
|
|
7
|
+
const program = new commander_1.Command();
|
|
8
|
+
program
|
|
9
|
+
.name('port')
|
|
10
|
+
.description('List listening TCP ports on the current machine')
|
|
11
|
+
.version('0.1.0');
|
|
12
|
+
program
|
|
13
|
+
.command('list')
|
|
14
|
+
.description('List all listening TCP ports with PID, port number, and program name')
|
|
15
|
+
.action(async () => {
|
|
16
|
+
try {
|
|
17
|
+
const entries = await (0, scanner_js_1.scanPorts)();
|
|
18
|
+
console.log((0, format_js_1.formatTable)(entries));
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.error('Error:', err instanceof Error ? err.message : err);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
// Show help if no command is given
|
|
26
|
+
program.parse();
|
|
27
|
+
if (!process.argv.slice(2).length) {
|
|
28
|
+
program.outputHelp();
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,6CAAyC;AACzC,2CAA0C;AAE1C,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,iDAAiD,CAAC;KAC9D,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,sEAAsE,CAAC;KACnF,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,IAAA,sBAAS,GAAE,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,IAAA,uBAAW,EAAC,OAAO,CAAC,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,mCAAmC;AACnC,OAAO,CAAC,KAAK,EAAE,CAAC;AAEhB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAClC,OAAO,CAAC,UAAU,EAAE,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scan = scan;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function execCommand(command, args) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
(0, child_process_1.execFile)(command, args, { timeout: 15000 }, (err, stdout) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
reject(err);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
resolve(stdout);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse `lsof -iTCP -sTCP:LISTEN -P -n` output.
|
|
19
|
+
* Example lines:
|
|
20
|
+
* COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
|
21
|
+
* node 12345 user 23u IPv4 0x1234abcd 0t0 TCP *:3000 (LISTEN)
|
|
22
|
+
* sshd 678 root 5u IPv6 0xabcd1234 0t0 TCP *:22 (LISTEN)
|
|
23
|
+
* nginx 9012 root 6u IPv4 0xdefg5678 0t0 TCP 127.0.0.1:8080 (LISTEN)
|
|
24
|
+
* java 5555 user 123u IPv6 0xaaaa1111 0t0 TCP [::1]:9090 (LISTEN)
|
|
25
|
+
*/
|
|
26
|
+
function parseLsof(output) {
|
|
27
|
+
const entries = [];
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
// Regex captures: COMMAND, PID, and the NAME field (address:port before (LISTEN))
|
|
30
|
+
const re = /^(\S+)\s+(\d+)\s+.*TCP\s+(\S+)\s+\(LISTEN\)/gm;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = re.exec(output)) !== null) {
|
|
33
|
+
const program = match[1];
|
|
34
|
+
const pid = parseInt(match[2], 10);
|
|
35
|
+
const nameField = match[3];
|
|
36
|
+
// Extract port from NAME field: *:3000, 127.0.0.1:8080, [::1]:9090
|
|
37
|
+
const portMatch = nameField.match(/:(\d+)$/);
|
|
38
|
+
if (!portMatch)
|
|
39
|
+
continue;
|
|
40
|
+
const port = parseInt(portMatch[1], 10);
|
|
41
|
+
const key = `${pid}:${port}`;
|
|
42
|
+
if (!seen.has(key)) {
|
|
43
|
+
seen.add(key);
|
|
44
|
+
entries.push({ pid, port, program });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return entries;
|
|
48
|
+
}
|
|
49
|
+
async function scan() {
|
|
50
|
+
try {
|
|
51
|
+
const stdout = await execCommand('lsof', ['-iTCP', '-sTCP:LISTEN', '-P', '-n']);
|
|
52
|
+
return parseLsof(stdout);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err?.code === 'ENOENT') {
|
|
56
|
+
throw new Error('`lsof` is not installed. Install it to use this tool:\n' +
|
|
57
|
+
' macOS: it should be available by default. If missing, install Xcode CLI tools.\n' +
|
|
58
|
+
' Linux: apt install lsof / yum install lsof');
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=darwin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"darwin.js","sourceRoot":"","sources":["../../src/parsers/darwin.ts"],"names":[],"mappings":";;AAoDA,oBAcC;AAlED,iDAAyC;AAGzC,SAAS,WAAW,CAAC,OAAe,EAAE,IAAc;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAA,wBAAQ,EAAC,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,GAAiB,EAAE,MAAc,EAAE,EAAE;YAChF,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,MAAc;IAC/B,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,kFAAkF;IAClF,MAAM,EAAE,GAAG,+CAA+C,CAAC;IAE3D,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAE3B,mEAAmE;QACnE,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS;YAAE,SAAS;QAEzB,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAEM,KAAK,UAAU,IAAI;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAChF,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,yDAAyD;gBACzD,oFAAoF;gBACpF,8CAA8C,CAC/C,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scan = scan;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function execCommand(command, args) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
(0, child_process_1.execFile)(command, args, { timeout: 15000 }, (err, stdout) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
reject(err);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
resolve(stdout);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse `ss -tlnp` output.
|
|
19
|
+
* Example lines:
|
|
20
|
+
* LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
|
|
21
|
+
* LISTEN 0 128 [::]:80 [::]:* users:(("nginx",pid=5678,fd=6))
|
|
22
|
+
*/
|
|
23
|
+
function parseSs(output) {
|
|
24
|
+
const entries = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
// Matches: address:port address:* users:(("name",pid=NNN,...
|
|
27
|
+
const re = /\S+:(\d+)\s+\S+:\*\s+users:\(\(\"(\S+)\",pid=(\d+)/g;
|
|
28
|
+
for (const line of output.split('\n')) {
|
|
29
|
+
let match;
|
|
30
|
+
while ((match = re.exec(line)) !== null) {
|
|
31
|
+
const port = parseInt(match[1], 10);
|
|
32
|
+
const program = match[2];
|
|
33
|
+
const pid = parseInt(match[3], 10);
|
|
34
|
+
const key = `${pid}:${port}`;
|
|
35
|
+
if (!seen.has(key)) {
|
|
36
|
+
seen.add(key);
|
|
37
|
+
entries.push({ pid, port, program });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return entries;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse `netstat -tlnp` output (fallback).
|
|
45
|
+
* Example lines:
|
|
46
|
+
* tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
|
|
47
|
+
* tcp6 0 0 :::80 :::* LISTEN 5678/nginx
|
|
48
|
+
*/
|
|
49
|
+
function parseNetstat(output) {
|
|
50
|
+
const entries = [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const re = /^\S+\s+\d+\s+\d+\s+\S+:(\d+)\s+\S+\s+LISTEN\s+(\d+)\/(\S+)/gm;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = re.exec(output)) !== null) {
|
|
55
|
+
const port = parseInt(match[1], 10);
|
|
56
|
+
const pid = parseInt(match[2], 10);
|
|
57
|
+
const program = match[3];
|
|
58
|
+
const key = `${pid}:${port}`;
|
|
59
|
+
if (!seen.has(key)) {
|
|
60
|
+
seen.add(key);
|
|
61
|
+
entries.push({ pid, port, program });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return entries;
|
|
65
|
+
}
|
|
66
|
+
async function scan() {
|
|
67
|
+
// Try `ss -tlnp` first
|
|
68
|
+
try {
|
|
69
|
+
const stdout = await execCommand('ss', ['-tlnp']);
|
|
70
|
+
return parseSs(stdout);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// ss failed, try netstat as fallback
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const stdout = await execCommand('netstat', ['-tlnp']);
|
|
77
|
+
return parseNetstat(stdout);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// netstat also failed
|
|
81
|
+
}
|
|
82
|
+
throw new Error('Neither `ss` nor `netstat` is available. ' +
|
|
83
|
+
'Install iproute2 (`ss`) or net-tools (`netstat`) to use this tool.');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=linux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linux.js","sourceRoot":"","sources":["../../src/parsers/linux.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scan = scan;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function execCommand(command, args) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
(0, child_process_1.execFile)(command, args, { timeout: 15000 }, (err, stdout) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
reject(err);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
resolve(stdout);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse `netstat -ano` output for LISTENING TCP ports.
|
|
19
|
+
* Example lines:
|
|
20
|
+
* TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 12345
|
|
21
|
+
* TCP 127.0.0.1:8080 0.0.0.0:0 LISTENING 5678
|
|
22
|
+
* TCP [::]:80 [::]:0 LISTENING 4
|
|
23
|
+
*/
|
|
24
|
+
function parseNetstat(output) {
|
|
25
|
+
// pid -> ports[]
|
|
26
|
+
const pidPorts = new Map();
|
|
27
|
+
const re = /^\s*(?:TCP|TCPv6)\s+\[?([^\]]+)\]?:(\d+)\s+.*LISTENING\s+(\d+)/gm;
|
|
28
|
+
let match;
|
|
29
|
+
while ((match = re.exec(output)) !== null) {
|
|
30
|
+
const port = parseInt(match[2], 10);
|
|
31
|
+
const pid = parseInt(match[3], 10);
|
|
32
|
+
const ports = pidPorts.get(pid);
|
|
33
|
+
if (ports) {
|
|
34
|
+
if (!ports.includes(port)) {
|
|
35
|
+
ports.push(port);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
pidPorts.set(pid, [port]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return pidPorts;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a single PID to a process name using `tasklist`.
|
|
46
|
+
* Example output: `"node.exe","12345","Console","1","123,456 K"`
|
|
47
|
+
*/
|
|
48
|
+
async function resolveProcessName(pid) {
|
|
49
|
+
// Special PIDs on Windows
|
|
50
|
+
if (pid === 0)
|
|
51
|
+
return 'System Idle Process';
|
|
52
|
+
if (pid === 4)
|
|
53
|
+
return 'System';
|
|
54
|
+
try {
|
|
55
|
+
const stdout = await execCommand('tasklist', [
|
|
56
|
+
'/FI', `PID eq ${pid}`,
|
|
57
|
+
'/FO', 'CSV',
|
|
58
|
+
'/NH',
|
|
59
|
+
]);
|
|
60
|
+
// Parse CSV: "program.exe","pid","session","session#","mem usage"
|
|
61
|
+
const match = stdout.match(/^"([^"]+)"/m);
|
|
62
|
+
if (match) {
|
|
63
|
+
return match[1];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// tasklist failed for this PID
|
|
68
|
+
}
|
|
69
|
+
return '<unknown>';
|
|
70
|
+
}
|
|
71
|
+
async function scan() {
|
|
72
|
+
// Phase 1: netstat -ano to get listening ports and PIDs
|
|
73
|
+
const netstatOutput = await execCommand('netstat', ['-ano']);
|
|
74
|
+
const pidPorts = parseNetstat(netstatOutput);
|
|
75
|
+
if (pidPorts.size === 0)
|
|
76
|
+
return [];
|
|
77
|
+
// Phase 2: resolve each PID to a process name
|
|
78
|
+
const entries = [];
|
|
79
|
+
for (const [pid, ports] of pidPorts) {
|
|
80
|
+
const program = await resolveProcessName(pid);
|
|
81
|
+
for (const port of ports) {
|
|
82
|
+
entries.push({ pid, port, program });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return entries;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=win32.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"win32.js","sourceRoot":"","sources":["../../src/parsers/win32.ts"],"names":[],"mappings":"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"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scanPorts = scanPorts;
|
|
4
|
+
async function scanPorts() {
|
|
5
|
+
switch (process.platform) {
|
|
6
|
+
case 'linux':
|
|
7
|
+
return (await import('./parsers/linux.js')).scan();
|
|
8
|
+
case 'darwin':
|
|
9
|
+
return (await import('./parsers/darwin.js')).scan();
|
|
10
|
+
case 'win32':
|
|
11
|
+
return (await import('./parsers/win32.js')).scan();
|
|
12
|
+
default:
|
|
13
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":";;AAEA,8BAWC;AAXM,KAAK,UAAU,SAAS;IAC7B,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,CAAC,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,KAAK,QAAQ;YACX,OAAO,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtD,KAAK,OAAO;YACV,OAAO,CAAC,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD;YACE,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjE,CAAC;AACH,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qzda/port",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cross-platform CLI tool to list listening TCP ports",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/qzda/port.git"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"port": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"start": "node dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^14.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"typescript": "^5.8.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|