@tertium/hlpr 0.5.8 → 0.6.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
@@ -45,126 +45,23 @@ hlpr -f ssh init dir
45
45
 
46
46
  ## Available Commands
47
47
 
48
- ### TypeScript Commands
49
-
50
- #### file rename
51
-
52
- Recursively rename files and folders according to a specific case style.
53
-
54
- **Supported styles:**
55
- - `title_underscore` - Title_Case_With_Underscores
56
- - `snake` - snake_case_lowercase
57
- - `kebab` - kebab-case-lowercase
58
- - `camel` - camelCaseLowerFirst
59
- - `pascal` - PascalCaseUpperFirst
60
- - `upper` - UPPER_CASE_WITH_UNDERSCORES
61
- - `lower` - lower_case_with_underscores
62
-
63
- **Usage:**
64
- ```bash
65
- # Dry run (preview changes without applying)
66
- hlpr file rename ./my-project kebab --dry
67
- hlpr file rename ./src snake -n
68
-
69
- # Apply changes
70
- hlpr file rename ./my-project kebab
71
- hlpr file rename ./docs pascal
72
- ```
73
-
74
- **Features:**
75
- - ✅ Recursive directory traversal
76
- - ✅ Extension preservation
77
- - ✅ Leading dot file support (e.g., `.gitignore`)
78
- - ✅ Case-only rename handling (Windows compatibility)
79
- - ✅ Conflict resolution (adds `_N` suffix)
80
- - ✅ Dry-run mode (`--dry` or `-n`)
81
-
82
- **See:** [`src/commands/file/rename/README.md`](src/commands/file/rename/README.md) for full documentation.
83
-
84
- ---
85
-
86
- #### help
48
+ See individual command documentation for detailed usage, options, and examples.
87
49
 
88
- Display help information about hlpr commands.
89
-
90
- **Usage:**
91
- ```bash
92
- hlpr help
93
- ```
50
+ ### TypeScript Commands
94
51
 
95
- ---
52
+ - **[file rename](src/commands/file/rename/README.md)** - Recursively rename files/folders with various case styles
53
+ - **[help](src/commands/help/README.md)** - Display help information about hlpr commands
54
+ - **[process list-port](src/commands/process/list-port/README.md)** - List processes running on a port
55
+ - **[process kill-port](src/commands/process/kill-port/README.md)** - Kill processes on a port
96
56
 
97
57
  ### Shell Script Commands
98
58
 
99
59
  Shell scripts support variable substitution using `{{variable}}` syntax.
100
60
 
101
- #### git
102
-
103
- Helpful git utilities for common development workflows.
104
-
105
- **Usage:**
106
- ```bash
107
- hlpr git <command>
108
- ```
109
-
110
- **Commands:**
111
- - `fodd` - Fetch and update develop branch from origin
112
- - `precommit` - Run build and stage binaries before commit
113
- - `switch-clean` - Safely switch to a branch and delete the previous one (local & remote)
114
-
115
- **See:** [`src/commands/git/README.md`](src/commands/git/README.md) for full documentation.
116
-
117
- ---
118
-
119
- #### hello
120
-
121
- Example/demo commands for testing the hlpr CLI tool.
122
-
123
- **Usage:**
124
- ```bash
125
- hlpr hello world
126
- ```
127
-
128
- Prompts for input and prints a personalized greeting with variable substitution.
129
-
130
- **See:** [`src/commands/hello/README.md`](src/commands/hello/README.md) for full documentation.
131
-
132
- ---
133
-
134
- #### nvm
135
-
136
- Node Version Manager utilities for managing Node.js versions.
137
-
138
- **Usage:**
139
- ```bash
140
- hlpr nvm <command>
141
- ```
142
-
143
- **Commands:**
144
- - `install` - Install Node Version Manager (nvm)
145
- - `lts` - Install and use Node.js LTS version
146
-
147
- **See:** [`src/commands/nvm/README.md`](src/commands/nvm/README.md) for full documentation.
148
-
149
- ---
150
-
151
- #### ssh
152
-
153
- SSH configuration and setup utilities.
154
-
155
- **Usage:**
156
- ```bash
157
- hlpr ssh init-dir
158
- ```
159
-
160
- Initializes SSH directory with proper permissions:
161
- - `~/.ssh/` directory with 700 permissions
162
- - `~/.ssh/known_hosts` with 644 permissions
163
- - `~/.ssh/config` with 644 permissions
164
-
165
- Portable across Unix/Linux/macOS and Git Bash on Windows.
166
-
167
- **See:** [`src/commands/ssh/README.md`](src/commands/ssh/README.md) for full documentation.
61
+ - **[git](src/commands/git/README.md)** - Git utilities (fodd, precommit, switch-clean)
62
+ - **[hello](src/commands/hello/README.md)** - Example greeting command with variable substitution
63
+ - **[nvm](src/commands/nvm/README.md)** - Node Version Manager utilities
64
+ - **[ssh](src/commands/ssh/README.md)** - SSH configuration and setup utilities
168
65
 
169
66
  ## How It Works
170
67
 
@@ -183,21 +80,40 @@ Portable across Unix/Linux/macOS and Git Bash on Windows.
183
80
  ## Directory Structure
184
81
 
185
82
  ```
186
- commands/
187
- ├── file/
188
- │ └── rename/
189
- ├── rename.ts
190
- ├── rename.test.ts
83
+ src/
84
+ ├── core/
85
+ │ └── command/
86
+ └── command.types.ts
87
+ ├── commands/
88
+ │ ├── file/
89
+ │ │ └── rename/
90
+ │ │ ├── rename.ts
91
+ │ │ ├── rename.test.ts
92
+ │ │ └── README.md
93
+ │ ├── git/
94
+ │ │ ├── fodd.sh
95
+ │ │ ├── precommit.sh
96
+ │ │ ├── switch-clean.sh
97
+ │ │ └── README.md
98
+ │ ├── hello/
99
+ │ │ ├── world.sh
100
+ │ │ └── README.md
101
+ │ ├── nvm/
102
+ │ │ ├── install.sh
103
+ │ │ ├── lts.sh
104
+ │ │ └── README.md
105
+ │ ├── process/
106
+ │ │ ├── kill-port/
107
+ │ │ │ ├── kill-port.ts
108
+ │ │ │ └── README.md
109
+ │ │ └── list-port/
110
+ │ │ ├── list-port.ts
111
+ │ │ └── README.md
112
+ │ └── ssh/
113
+ │ ├── init-dir.sh
191
114
  │ └── README.md
192
- ├── git/
193
- └── fodd.sh
194
- ├── hello/
195
- │ └── world.sh
196
- ├── nvm/
197
- │ ├── install.sh
198
- │ └── lts.sh
199
- └── ssh/
200
- └── initdir.sh
115
+ ├── index.ts
116
+ └── commands.ts
201
117
  ```
202
118
 
203
119
  ## Adding Your Own Scripts
@@ -246,6 +162,113 @@ The utility maps command arguments to script files:
246
162
 
247
163
  - `hlpr file rename <args>` → runs `commands/file/rename/rename.ts`
248
164
 
165
+ ## How Shell Scripts (.sh) Work
166
+
167
+ Shell scripts are bash scripts stored in `src/commands/<category>/` directories. They support interactive variable substitution and are cross-platform compatible.
168
+
169
+ ### Variable Substitution
170
+
171
+ Shell scripts can use `{{variable}}` placeholders for user input:
172
+
173
+ ```bash
174
+ #!/bin/bash
175
+ # Example: src/commands/greet/hello.sh
176
+ echo "Hello {{name}}, welcome to {{place}}"
177
+ ```
178
+
179
+ When you run `hlpr greet hello`, the utility:
180
+ 1. Reads the script
181
+ 2. Finds all `{{variable}}` placeholders
182
+ 3. Prompts you to enter values for each variable
183
+ 4. Executes the script with variables replaced
184
+
185
+ **Example interaction:**
186
+ ```
187
+ $ hlpr greet hello
188
+ Enter value for name: Alice
189
+ Enter value for place: Wonderland
190
+ Hello Alice, welcome to Wonderland
191
+ ```
192
+
193
+ ### Script Execution
194
+
195
+ - Each line of the script is executed sequentially
196
+ - If any line fails and `-f` flag is not set, execution stops
197
+ - Scripts have access to standard bash features (pipes, redirects, etc.)
198
+
199
+ ### Windows Compatibility
200
+
201
+ `.sh` scripts run on Windows through **PowerShell** and the modern cross-platform command stack.
202
+
203
+ **How it works:**
204
+
205
+ 1. User runs: `hlpr git fodd`
206
+ 2. hlpr detects the platform is Windows
207
+ 3. hlpr invokes **PowerShell** to execute the `.sh` script
208
+ 4. PowerShell (v7+) and installed tools provide Unix-like command support
209
+ 5. Commands like `grep`, `sed`, `ls`, etc. work through various implementations
210
+
211
+ **Why this works:**
212
+
213
+ Modern Windows systems have multiple sources for Unix-like commands:
214
+
215
+ - **PowerShell 7+** - Cross-platform, provides built-in Unix-like commands and aliases (ls, grep, select-string, etc.)
216
+ - **Git** - Installs Unix utilities as part of its toolchain
217
+ - **Node.js** - Provides command-line tools that work cross-platform
218
+ - **Package managers** - scoop, chocolatey, and winget provide Unix tools
219
+ - **WSL 2** - Windows Subsystem for Linux provides full Linux compatibility
220
+
221
+ Scripts work because:
222
+ - Modern tools use consistent command-line interfaces across platforms
223
+ - PowerShell Core has native cross-platform support
224
+ - Common utilities are available through multiple sources in system PATH
225
+
226
+ **Example:**
227
+
228
+ ```bash
229
+ #!/bin/bash
230
+ # This script works on Windows, Linux, and macOS
231
+ echo "Running on: $(uname -s || echo 'Windows PowerShell')"
232
+ if [ -d "./src" ]; then
233
+ echo "Found src directory"
234
+ ls src | head -5
235
+ fi
236
+ ```
237
+
238
+ **Requirements:**
239
+
240
+ - **Modern PowerShell** (v7+) is recommended for full compatibility
241
+ - **Windows 10+** with default PowerShell Core support
242
+ - **Git** installed (provides additional Unix utilities)
243
+ - **Node.js** for runtime support
244
+
245
+ No special configuration needed - hlpr automatically detects and uses PowerShell on Windows.
246
+
247
+ ### Creating Shell Scripts
248
+
249
+ 1. Create directory: `src/commands/<category>/`
250
+ 2. Add `.sh` file: `<command>.sh`
251
+ 3. Add shebang: `#!/bin/bash`
252
+ 4. Use `{{variable}}` for user input
253
+ 5. Build with `bun run build` (copies `.sh` files to `bin/commands/`)
254
+
255
+ Example:
256
+
257
+ ```bash
258
+ #!/bin/bash
259
+ # src/commands/greet/hello.sh
260
+ NAME={{name}}
261
+ echo "Hello $NAME!"
262
+ echo "Today is $(date '+%A')"
263
+ ```
264
+
265
+ ### Building and Distribution
266
+
267
+ Shell scripts are:
268
+ - **Copied** (not compiled) during build to `bin/commands/`
269
+ - **Distributed** as part of the published package
270
+ - **Platform-compatible** across Linux, macOS, and Windows (with Git Bash)
271
+
249
272
  ## Error Handling
250
273
 
251
274
  By default, the utility stops execution if any command fails. Use the `-f` flag to continue execution despite failures:
@@ -263,10 +286,14 @@ hlpr -f ssh init dir
263
286
  ```bash
264
287
  # Clone the repository
265
288
  git clone https://github.com/tertiumnon/hlpr.git
289
+ cd hlpr
266
290
 
267
291
  # Install dependencies
268
292
  npm install
269
293
 
294
+ # Setup git hooks (runs tests before commit)
295
+ npm run setup:hooks
296
+
270
297
  # Build the project
271
298
  bun run build
272
299
 
@@ -274,6 +301,20 @@ bun run build
274
301
  npm link
275
302
  ```
276
303
 
304
+ ### Git Hooks
305
+
306
+ This project uses native git hooks (`.githooks/pre-commit`) to run tests before each commit. After cloning or pulling, ensure hooks are configured:
307
+
308
+ ```bash
309
+ npm run setup:hooks
310
+ ```
311
+
312
+ The pre-commit hook:
313
+ - Runs unit and integration tests
314
+ - Runs end-to-end tests
315
+ - Aborts commit if tests fail
316
+ - Can be skipped with `SKIP_E2E=1 git commit` (not recommended)
317
+
277
318
  ## License
278
319
 
279
320
  MIT
@@ -1,3 +1,4 @@
1
+ // @bun
1
2
  // src/commands/file/rename/rename.ts
2
3
  import fs from "fs/promises";
3
4
  import path from "path";
@@ -21,7 +22,7 @@ function transformBasename(basename, style) {
21
22
  return basename;
22
23
  switch (style) {
23
24
  case "title_underscore":
24
- return leadingDot + words.map(cap).join("_") + ext;
25
+ return leadingDot + titleCase(words).join("_") + ext;
25
26
  case "snake":
26
27
  return leadingDot + words.map((w) => w.toLowerCase()).join("_") + ext;
27
28
  case "kebab":
@@ -31,7 +32,7 @@ function transformBasename(basename, style) {
31
32
  case "pascal":
32
33
  return leadingDot + words.map(cap).join("") + ext;
33
34
  case "pascal_underscore":
34
- return leadingDot + words.map(cap).join("_") + ext;
35
+ return leadingDot + titleCase(words).join("_") + ext;
35
36
  case "upper":
36
37
  return leadingDot + words.join("_").toUpperCase() + ext;
37
38
  case "lower":
@@ -40,11 +41,48 @@ function transformBasename(basename, style) {
40
41
  return leadingDot + core + ext;
41
42
  }
42
43
  }
44
+ function isAllCaps(s) {
45
+ return s.length > 0 && s === s.toUpperCase() && s !== s.toLowerCase();
46
+ }
43
47
  function cap(s) {
44
48
  if (!s)
45
49
  return s;
46
50
  return s[0].toUpperCase() + s.slice(1).toLowerCase();
47
51
  }
52
+ function titleCase(words) {
53
+ const smallWords = new Set([
54
+ "a",
55
+ "an",
56
+ "and",
57
+ "as",
58
+ "at",
59
+ "but",
60
+ "by",
61
+ "for",
62
+ "from",
63
+ "in",
64
+ "into",
65
+ "nor",
66
+ "of",
67
+ "on",
68
+ "or",
69
+ "the",
70
+ "to",
71
+ "with"
72
+ ]);
73
+ return words.map((word, index) => {
74
+ if (isAllCaps(word)) {
75
+ return word;
76
+ }
77
+ if (index === 0) {
78
+ return cap(word);
79
+ }
80
+ if (smallWords.has(word.toLowerCase())) {
81
+ return word.toLowerCase();
82
+ }
83
+ return cap(word);
84
+ });
85
+ }
48
86
  async function exists(p) {
49
87
  try {
50
88
  await fs.access(p);
@@ -229,7 +267,7 @@ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
229
267
  console.log(`Renamed ${performed.length} items:`);
230
268
  }
231
269
  performed.forEach(({ from, to }) => {
232
- console.log(` ${from} ${to}`);
270
+ console.log(` ${from} \u2192 ${to}`);
233
271
  });
234
272
  }).catch((err) => {
235
273
  console.error("Error:", err);
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/commands/process/kill-port/kill-port.ts
5
+ import { execSync, spawnSync } from "child_process";
6
+ import { platform } from "os";
7
+ function getProcessOnWindowsPort(port) {
8
+ try {
9
+ const output = execSync(`netstat -ano | findstr :${port}`, { encoding: "utf-8" });
10
+ const pids = [];
11
+ const lines = output.split(`
12
+ `);
13
+ for (const line of lines) {
14
+ const match = line.match(/\s+(\d+)\s*$/);
15
+ if (match) {
16
+ const pid = parseInt(match[1], 10);
17
+ if (pid > 0 && !pids.includes(pid)) {
18
+ pids.push(pid);
19
+ }
20
+ }
21
+ }
22
+ return pids;
23
+ } catch (error) {
24
+ return [];
25
+ }
26
+ }
27
+ function getProcessOnLinuxPort(port) {
28
+ try {
29
+ const output = execSync(`lsof -i :${port} -n -P`, { encoding: "utf-8" });
30
+ const pids = [];
31
+ const lines = output.split(`
32
+ `);
33
+ for (let i = 1;i < lines.length; i++) {
34
+ const parts = lines[i].trim().split(/\s+/);
35
+ if (parts.length > 1) {
36
+ const pid = parseInt(parts[1], 10);
37
+ if (!isNaN(pid) && !pids.includes(pid)) {
38
+ pids.push(pid);
39
+ }
40
+ }
41
+ }
42
+ return pids;
43
+ } catch {
44
+ try {
45
+ const output = execSync(`netstat -tulnp 2>/dev/null | grep :${port}`, { encoding: "utf-8" });
46
+ const pids = [];
47
+ const lines = output.split(`
48
+ `);
49
+ for (const line of lines) {
50
+ const match = line.match(/(\d+)\//);
51
+ if (match) {
52
+ const pid = parseInt(match[1], 10);
53
+ if (!pids.includes(pid)) {
54
+ pids.push(pid);
55
+ }
56
+ }
57
+ }
58
+ return pids;
59
+ } catch {
60
+ return [];
61
+ }
62
+ }
63
+ }
64
+ function killProcessOnWindows(pid, force, verbose) {
65
+ try {
66
+ const args = force ? ["/PID", pid.toString(), "/F"] : ["/PID", pid.toString()];
67
+ const result = spawnSync("taskkill", args, { encoding: "utf-8" });
68
+ if (result.status === 0) {
69
+ if (verbose)
70
+ console.log(`\u2713 Killed process ${pid}`);
71
+ return true;
72
+ } else {
73
+ if (verbose)
74
+ console.error(`\u2717 Failed to kill process ${pid}: ${result.stderr}`);
75
+ return false;
76
+ }
77
+ } catch (error) {
78
+ if (verbose)
79
+ console.error(`\u2717 Error killing process ${pid}: ${error}`);
80
+ return false;
81
+ }
82
+ }
83
+ function killProcessOnLinux(pid, force, verbose) {
84
+ try {
85
+ const signal = force ? "SIGKILL" : "SIGTERM";
86
+ const result = spawnSync("kill", [`-${signal}`, pid.toString()], { encoding: "utf-8" });
87
+ if (result.status === 0) {
88
+ if (verbose)
89
+ console.log(`\u2713 Killed process ${pid} with ${signal}`);
90
+ return true;
91
+ } else {
92
+ if (verbose)
93
+ console.error(`\u2717 Failed to kill process ${pid}: ${result.stderr}`);
94
+ return false;
95
+ }
96
+ } catch (error) {
97
+ if (verbose)
98
+ console.error(`\u2717 Error killing process ${pid}: ${error}`);
99
+ return false;
100
+ }
101
+ }
102
+ async function killPort(port, options = {}) {
103
+ const { force = false, verbose = false } = options;
104
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
105
+ throw new Error(`Invalid port number: ${port}. Port must be between 1 and 65535.`);
106
+ }
107
+ const os = platform();
108
+ let pids = [];
109
+ if (verbose)
110
+ console.log(`Searching for processes on port ${port} (${os})...`);
111
+ if (os === "win32") {
112
+ pids = getProcessOnWindowsPort(port);
113
+ } else {
114
+ pids = getProcessOnLinuxPort(port);
115
+ }
116
+ if (pids.length === 0) {
117
+ if (verbose)
118
+ console.log(`No processes found on port ${port}`);
119
+ return { killed: [], failed: [] };
120
+ }
121
+ if (verbose)
122
+ console.log(`Found ${pids.length} process(es) on port ${port}: ${pids.join(", ")}`);
123
+ const killed = [];
124
+ const failed = [];
125
+ for (const pid of pids) {
126
+ let success;
127
+ if (os === "win32") {
128
+ success = killProcessOnWindows(pid, force, verbose);
129
+ } else {
130
+ success = killProcessOnLinux(pid, force, verbose);
131
+ }
132
+ if (success) {
133
+ killed.push(pid);
134
+ } else {
135
+ failed.push(pid);
136
+ }
137
+ }
138
+ return { killed, failed };
139
+ }
140
+ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
141
+ const args = process.argv.slice(2);
142
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
143
+ console.log("Usage: kill-port <port> [options]");
144
+ console.log("Kill process(es) running on a specified port (Windows & Linux support)");
145
+ console.log();
146
+ console.log("Arguments:");
147
+ console.log(" port Port number (1-65535)");
148
+ console.log();
149
+ console.log("Options:");
150
+ console.log(" -f, --force Force kill (SIGKILL on Linux, /F on Windows)");
151
+ console.log(" -v, --verbose Show verbose output");
152
+ console.log(" -h, --help Show this help message");
153
+ console.log();
154
+ console.log("Examples:");
155
+ console.log(" hlpr process kill-port 3000");
156
+ console.log(" hlpr process kill-port 8080 --force");
157
+ console.log(" hlpr process kill-port 5432 -v");
158
+ process.exit(0);
159
+ }
160
+ const port = parseInt(args[0], 10);
161
+ const force = args.includes("-f") || args.includes("--force");
162
+ const verbose = args.includes("-v") || args.includes("--verbose");
163
+ killPort(port, { force, verbose }).then((result) => {
164
+ if (result.killed.length > 0) {
165
+ console.log(`Successfully killed ${result.killed.length} process(es) on port ${port}`);
166
+ }
167
+ if (result.failed.length > 0) {
168
+ console.error(`Failed to kill ${result.failed.length} process(es): ${result.failed.join(", ")}`);
169
+ process.exit(1);
170
+ }
171
+ if (result.killed.length === 0 && result.failed.length === 0) {
172
+ console.log(`No processes found on port ${port}`);
173
+ }
174
+ process.exit(0);
175
+ }).catch((error) => {
176
+ console.error("Error:", error.message);
177
+ process.exit(1);
178
+ });
179
+ }
180
+ var kill_port_default = { killPort };
181
+ export {
182
+ killPort,
183
+ kill_port_default as default
184
+ };
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/commands/process/list-port/list-port.ts
5
+ import { execSync } from "child_process";
6
+ import { platform } from "os";
7
+ function getProcessesOnWindowsPort(port) {
8
+ try {
9
+ const output = execSync(`netstat -ano | findstr :${port}`, { encoding: "utf-8" });
10
+ const processes = [];
11
+ const lines = output.split(`
12
+ `);
13
+ const pids = new Set;
14
+ for (const line of lines) {
15
+ const match = line.match(/\s+(\d+)\s*$/);
16
+ if (match) {
17
+ const pid = parseInt(match[1], 10);
18
+ if (pid > 0 && !pids.has(pid)) {
19
+ pids.add(pid);
20
+ try {
21
+ const taskOutput = execSync(`tasklist /FI "PID eq ${pid}" /NH /FO CSV`, { encoding: "utf-8" }).trim();
22
+ const parts = taskOutput.split('","');
23
+ const command = parts[0].replace(/^"|"$/g, "");
24
+ processes.push({
25
+ pid,
26
+ state: "LISTENING",
27
+ command
28
+ });
29
+ } catch {
30
+ processes.push({
31
+ pid,
32
+ state: "LISTENING"
33
+ });
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return processes;
39
+ } catch (error) {
40
+ return [];
41
+ }
42
+ }
43
+ function getProcessesOnLinuxPort(port) {
44
+ try {
45
+ const output = execSync(`lsof -i :${port} -n -P`, { encoding: "utf-8" });
46
+ const processes = [];
47
+ const lines = output.split(`
48
+ `);
49
+ const pids = new Set;
50
+ for (let i = 1;i < lines.length; i++) {
51
+ const parts = lines[i].trim().split(/\s+/);
52
+ if (parts.length > 2) {
53
+ const command = parts[0];
54
+ const pid = parseInt(parts[1], 10);
55
+ const state = parts[7] || "LISTENING";
56
+ if (!isNaN(pid) && pid > 0 && !pids.has(pid)) {
57
+ pids.add(pid);
58
+ processes.push({
59
+ pid,
60
+ state,
61
+ command
62
+ });
63
+ }
64
+ }
65
+ }
66
+ return processes;
67
+ } catch {
68
+ try {
69
+ const output = execSync(`netstat -tulnp 2>/dev/null | grep :${port}`, { encoding: "utf-8" });
70
+ const processes = [];
71
+ const lines = output.split(`
72
+ `);
73
+ const pids = new Set;
74
+ for (const line of lines) {
75
+ const match = line.match(/(\d+)\/(.+)/);
76
+ if (match) {
77
+ const pid = parseInt(match[1], 10);
78
+ const command = match[2];
79
+ if (!pids.has(pid)) {
80
+ pids.add(pid);
81
+ processes.push({
82
+ pid,
83
+ state: "LISTENING",
84
+ command
85
+ });
86
+ }
87
+ }
88
+ }
89
+ return processes;
90
+ } catch {
91
+ return [];
92
+ }
93
+ }
94
+ }
95
+ function formatTable(processes) {
96
+ if (processes.length === 0) {
97
+ return "No processes found";
98
+ }
99
+ const pidWidth = 10;
100
+ const stateWidth = 12;
101
+ const commandWidth = 50;
102
+ const header = `${"PID".padEnd(pidWidth)} ${"STATE".padEnd(stateWidth)} COMMAND`;
103
+ const separator = "\u2500".repeat(pidWidth + stateWidth + commandWidth + 2);
104
+ const rows = processes.map((p) => {
105
+ const command = p.command || "unknown";
106
+ const truncated = command.length > commandWidth ? command.substring(0, commandWidth - 3) + "..." : command;
107
+ return `${p.pid.toString().padEnd(pidWidth)} ${p.state.padEnd(stateWidth)} ${truncated}`;
108
+ });
109
+ return `${header}
110
+ ${separator}
111
+ ${rows.join(`
112
+ `)}`;
113
+ }
114
+ async function listPort(port, options = {}) {
115
+ const { verbose = false } = options;
116
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
117
+ throw new Error(`Invalid port number: ${port}. Port must be between 1 and 65535.`);
118
+ }
119
+ const os = platform();
120
+ if (verbose)
121
+ console.log(`Searching for processes on port ${port} (${os})...
122
+ `);
123
+ let processes = [];
124
+ if (os === "win32") {
125
+ processes = getProcessesOnWindowsPort(port);
126
+ } else {
127
+ processes = getProcessesOnLinuxPort(port);
128
+ }
129
+ return processes;
130
+ }
131
+ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
132
+ const args = process.argv.slice(2);
133
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
134
+ console.log("Usage: list-port <port> [options]");
135
+ console.log("List process(es) running on a specified port (Windows & Linux support)");
136
+ console.log();
137
+ console.log("Arguments:");
138
+ console.log(" port Port number (1-65535)");
139
+ console.log();
140
+ console.log("Options:");
141
+ console.log(" -v, --verbose Show verbose output");
142
+ console.log(" -h, --help Show this help message");
143
+ console.log();
144
+ console.log("Examples:");
145
+ console.log(" hlpr process list-port 3000");
146
+ console.log(" hlpr process list-port 8080 --verbose");
147
+ console.log(" hlpr process list-port 5432 -v");
148
+ process.exit(0);
149
+ }
150
+ const port = parseInt(args[0], 10);
151
+ const verbose = args.includes("-v") || args.includes("--verbose");
152
+ listPort(port, { verbose }).then((processes) => {
153
+ console.log(formatTable(processes));
154
+ process.exit(processes.length === 0 ? 1 : 0);
155
+ }).catch((error) => {
156
+ console.error("Error:", error.message);
157
+ process.exit(1);
158
+ });
159
+ }
160
+ var list_port_default = { listPort };
161
+ export {
162
+ listPort,
163
+ list_port_default as default
164
+ };
package/bin/index.js CHANGED
@@ -23,7 +23,7 @@ async function getVersion() {
23
23
  function detectShell() {
24
24
  const isWindows = os.platform() === "win32";
25
25
  if (isWindows) {
26
- return "bash";
26
+ return "powershell";
27
27
  }
28
28
  return "bash";
29
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tertium/hlpr",
3
- "version": "0.5.8",
3
+ "version": "0.6.0",
4
4
  "description": "Windows and *Nix utility for typical programming activity",
5
5
  "author": "Vitalii Balabanov",
6
6
  "email": "tertiumnon@gmail.com",
@@ -8,22 +8,21 @@
8
8
  "type": "module",
9
9
  "scripts": {
10
10
  "build:main": "bun build ./src/index.ts --outfile ./bin/index.js --target node",
11
- "build:commands": "bun build-commands.js",
11
+ "build:commands": "bun scripts/build-commands/build-commands.ts",
12
12
  "build": "bun run build:main && bun run build:commands",
13
13
  "test": "bun test",
14
- "test:e2e": "bun scripts/test-rename-e2e.cjs",
14
+ "test:e2e": "bun scripts/test-rename/test-rename.ts",
15
15
  "prepublishOnly": "bun run build",
16
- "release:minor": "bun node_modules/@tertium/js/scripts/release.js minor",
17
16
  "release:patch": "bun node_modules/@tertium/js/scripts/release.js patch",
17
+ "release:minor": "bun node_modules/@tertium/js/scripts/release.js minor",
18
18
  "release:major": "bun node_modules/@tertium/js/scripts/release.js major",
19
- "prepare": "husky"
19
+ "setup:hooks": "git config core.hooksPath .githooks"
20
20
  },
21
21
  "dependencies": {
22
22
  "@tertium/js": "^1.4.7"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^22.13.10",
26
- "husky": "^9.1.7",
27
26
  "typescript": "^5.8.3"
28
27
  },
29
28
  "bin": {