@tertium/hlpr 0.5.8 → 0.6.1

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
@@ -0,0 +1,225 @@
1
+ # Rename Command
2
+
3
+ Recursively rename files and folders according to a specific case style.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ hlpr file rename <directory> <style> [--dry|-n] [--no-update-content]
9
+ ```
10
+
11
+ ### Arguments
12
+
13
+ - `<directory>` - The root directory to recursively process
14
+ - `<style>` - The target case style (see Supported Styles below)
15
+ - `--dry` or `-n` - Optional dry-run mode (preview changes without applying)
16
+ - `--no-update-content` - Skip updating import/require statements in files (content updates are enabled by default)
17
+
18
+ ## Supported Styles
19
+
20
+ | Style | Description | Example |
21
+ |-------|-------------|---------|
22
+ | `title_underscore` | Title case with underscores | `File_Name` |
23
+ | `pascal_underscore` | Pascal-style with underscores (alias `title_underscore`) | `File_Name` |
24
+ | `snake` | Lowercase with underscores | `file_name` |
25
+ | `kebab` | Lowercase with hyphens | `file-name` |
26
+ | `camel` | Lowercase first word, capitalized rest | `fileName` |
27
+ | `pascal` | All words capitalized, no separators | `FileName` |
28
+ | `upper` | Uppercase with underscores | `FILE_NAME` |
29
+ | `lower` | Lowercase with underscores | `file_name` |
30
+
31
+ ## Examples
32
+
33
+ ### Dry Run (Preview Changes)
34
+
35
+ ```bash
36
+ # Preview renaming all files/folders to kebab-case
37
+ hlpr file rename ./my-project kebab --dry
38
+
39
+ # Preview renaming with short flag
40
+ hlpr file rename ./src snake -n
41
+ ```
42
+
43
+ ### Apply Changes
44
+
45
+ ```bash
46
+ # Rename all files/folders to kebab-case
47
+ hlpr file rename ./my-project kebab
48
+
49
+ # Rename to PascalCase
50
+ hlpr file rename ./docs pascal
51
+
52
+ # Rename current directory
53
+ hlpr file rename . snake
54
+ hlpr rename . snake
55
+ ```
56
+
57
+ ## Features
58
+
59
+ ### ✅ Automatic Content Updates
60
+
61
+ **NEW:** By default, the rename command now automatically updates import and require statements in your code files when files are renamed!
62
+
63
+ The tool intelligently detects and updates **all text-based files** by analyzing file content (not just extensions). This includes:
64
+
65
+ - Source code: `.js`, `.ts`, `.jsx`, `.tsx`, `.py`, `.rb`, `.go`, `.rs`, etc.
66
+ - Config files: `.json`, `.yml`, `.yaml`, `.toml`, `.xml`, `.ini`, etc.
67
+ - Documentation: `.md`, `.txt`, `.rst`, etc.
68
+ - Shell scripts: `.sh`, `.bash`, `.zsh`, etc.
69
+ - And any other text file format!
70
+
71
+ Binary files (images, executables, etc.) are automatically skipped.
72
+
73
+ Example:
74
+
75
+ ```javascript
76
+ // Before renaming from kebab-case to snake_case:
77
+ import { helper } from './my-utils'
78
+ const x = require('./my-utils')
79
+
80
+ // After running: hlpr file rename . snake
81
+ import { helper } from './my_utils'
82
+ const x = require('./my_utils')
83
+ ```
84
+
85
+ The feature supports:
86
+
87
+ - Single quotes, double quotes, and backticks
88
+ - Relative paths (`./`, `../`)
89
+ - Files with and without extensions in import statements
90
+ - Both ES6 imports and CommonJS require statements
91
+
92
+ To disable this feature, use the `--no-update-content` flag.
93
+
94
+ ### ✅ Recursive Processing
95
+
96
+ The command walks through all subdirectories, renaming:
97
+
98
+ 1. Files first (so they don't interfere with directory renames)
99
+ 2. Directories after processing their contents
100
+ 3. Updates file content references after renaming (unless disabled)
101
+
102
+ ### ✅ Extension Preservation
103
+
104
+ File extensions are preserved. Only the base name is transformed:
105
+
106
+ - `file-name.md` → `file_name.md` (snake case)
107
+ - `MyComponent.tsx` → `my-component.tsx` (kebab case)
108
+
109
+ ### ✅ Leading Dot Files
110
+
111
+ Files starting with a dot (e.g., `.gitignore`, `.env`) are handled specially:
112
+
113
+ - The leading dot is preserved
114
+ - The rest of the name is transformed
115
+ - `.gitIgnore` → `.git-ignore` (kebab case)
116
+
117
+ ### ✅ Case-Only Renames (Windows)
118
+
119
+ Windows filesystems are case-insensitive, so renaming `File.txt` to `file.txt` requires special handling. The command uses a temporary file strategy to handle this correctly.
120
+
121
+ ### ✅ Conflict Resolution
122
+
123
+ If a renamed file would conflict with an existing file, the command automatically adds a `_N` suffix:
124
+
125
+ - If `file-name.txt` → `file_name.txt` already exists
126
+ - The new file becomes `file_name_1.txt`
127
+
128
+ ### ✅ Dry-Run Mode
129
+
130
+ Use `--dry` or `-n` to preview all changes before applying them:
131
+
132
+ ```bash
133
+ hlpr file rename ./src kebab --dry
134
+ ```
135
+
136
+ Output:
137
+
138
+ ```text
139
+ Dry run - would rename 3 items:
140
+ src/myFile.ts → src/my-file.ts
141
+ src/anotherFile.ts → src/another-file.ts
142
+ src/someFolder → src/some-folder
143
+ ```
144
+
145
+ ## TypeScript API
146
+
147
+ The command can also be used programmatically:
148
+
149
+ ```typescript
150
+ import { renameRecursive, transformBasename } from './commands/rename/rename.js'
151
+
152
+ // Transform a single basename
153
+ const newName = transformBasename('MyFileName', 'kebab')
154
+ // Returns: 'my-file-name'
155
+
156
+ // Recursively rename with dry-run
157
+ const changes = await renameRecursive('./my-project', 'snake', { dryRun: true })
158
+ // Returns: [{ from: '...', to: '...' }, ...]
159
+
160
+ // Apply changes with content updates (default)
161
+ await renameRecursive('./my-project', 'kebab')
162
+
163
+ // Apply changes without updating file contents
164
+ await renameRecursive('./my-project', 'kebab', { updateContent: false })
165
+
166
+ // Combine options
167
+ await renameRecursive('./my-project', 'snake', {
168
+ dryRun: false,
169
+ updateContent: true
170
+ })
171
+ ```
172
+
173
+ ## Technical Details
174
+
175
+ ### Word Splitting
176
+
177
+ The command intelligently splits filenames into words by:
178
+
179
+ 1. Splitting on non-alphanumeric characters (hyphens, underscores, spaces, etc.)
180
+ 2. Splitting on CamelCase boundaries (`myFileName` → `my`, `File`, `Name`)
181
+ 3. Preserving Unicode letters and numbers
182
+
183
+ ### Transformation Algorithm
184
+
185
+ 1. Walk directory tree recursively
186
+ 2. For each file:
187
+ - Extract basename (without extension)
188
+ - Transform basename according to style
189
+ - Append original extension
190
+ - Rename file (handling case-only renames and conflicts)
191
+ 3. For each directory (after processing contents):
192
+ - Transform directory name according to style
193
+ - Rename directory (handling case-only renames and conflicts)
194
+
195
+ ### Error Handling
196
+
197
+ - If a rename fails, the command attempts to revert temporary changes
198
+ - All errors are logged to stderr
199
+ - The command exits with code 1 on failure
200
+
201
+ ## Limitations
202
+
203
+ - Files and directories must be accessible (proper permissions)
204
+ - Very long paths (>260 characters on Windows) may fail
205
+ - Symlinks are not followed (treated as regular files/directories)
206
+
207
+ ## Examples by Style
208
+
209
+ Starting with: `MyTestFile-name_Example.txt`
210
+
211
+ | Style | Result |
212
+ |-------|--------|
213
+ | `title_underscore` | `My_Test_File_Name_Example.txt` |
214
+ | `snake` | `my_test_file_name_example.txt` |
215
+ | `kebab` | `my-test-file-name-example.txt` |
216
+ | `camel` | `myTestFileNameExample.txt` |
217
+ | `pascal` | `MyTestFileNameExample.txt` |
218
+ | `upper` | `MY_TEST_FILE_NAME_EXAMPLE.txt` |
219
+ | `lower` | `my_test_file_name_example.txt` |
220
+
221
+ ## See Also
222
+
223
+ - [Main hlpr README](../../README.md)
224
+ - [TypeScript Implementation](./rename.ts)
225
+ - [Tests](./rename.test.ts)
@@ -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);