@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 +168 -127
- package/bin/commands/file/rename/README.md +225 -0
- package/bin/commands/file/rename/rename.js +41 -3
- package/bin/commands/git/README.md +129 -0
- package/bin/commands/hello/README.md +35 -0
- package/bin/commands/help/README.md +55 -0
- package/bin/commands/nvm/README.md +90 -0
- package/bin/commands/process/kill-port/README.md +49 -0
- package/bin/commands/process/kill-port/kill-port.js +184 -0
- package/bin/commands/process/list-port/README.md +58 -0
- package/bin/commands/process/list-port/list-port.js +164 -0
- package/bin/commands/ssh/README.md +80 -0
- package/bin/index.js +13 -16
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -45,126 +45,23 @@ hlpr -f ssh init dir
|
|
|
45
45
|
|
|
46
46
|
## Available Commands
|
|
47
47
|
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
187
|
-
├──
|
|
188
|
-
│ └──
|
|
189
|
-
│
|
|
190
|
-
|
|
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
|
-
├──
|
|
193
|
-
|
|
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
|
|
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
|
|
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}
|
|
270
|
+
console.log(` ${from} \u2192 ${to}`);
|
|
233
271
|
});
|
|
234
272
|
}).catch((err) => {
|
|
235
273
|
console.error("Error:", err);
|