@tertium/hlpr 0.3.5 → 0.4.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 +10 -0
- package/bin/commands/file/rename/rename.js +82 -3
- package/bin/commands/git/fodd.sh +3 -0
- package/bin/commands/git/precommit.sh +3 -0
- package/bin/commands/git/switch-clean.sh +68 -0
- package/bin/commands/hello/world.sh +3 -0
- package/bin/commands/nvm/install.sh +3 -0
- package/bin/commands/nvm/lts.sh +3 -0
- package/bin/commands/ssh/init-dir.sh +11 -1
- package/bin/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI utility for running shell scripts with variable substitution and TypeScript-based commands.
|
|
4
4
|
|
|
5
|
+
## Platform Support
|
|
6
|
+
|
|
7
|
+
hlpr works on all major operating systems:
|
|
8
|
+
|
|
9
|
+
- ✅ **Linux** - Fully supported
|
|
10
|
+
- ✅ **macOS** - Fully supported
|
|
11
|
+
- ✅ **Windows** - Requires [Git for Windows](https://git-scm.com/download/win) (includes Git Bash)
|
|
12
|
+
|
|
13
|
+
**Note for Windows users:** Shell script commands (`.sh` files) require Bash, which is included with Git for Windows. TypeScript commands work on all platforms.
|
|
14
|
+
|
|
5
15
|
## Installation
|
|
6
16
|
|
|
7
17
|
```bash
|
|
@@ -87,8 +87,61 @@ async function safeRename(oldPath, newPath) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
async function updateFileReferences(filePath, renames) {
|
|
91
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
92
|
+
let updated = content;
|
|
93
|
+
for (const { from, to } of renames) {
|
|
94
|
+
const fromBasename = path.basename(from);
|
|
95
|
+
const toBasename = path.basename(to);
|
|
96
|
+
if (fromBasename === toBasename)
|
|
97
|
+
continue;
|
|
98
|
+
const fromWithoutExt = fromBasename.replace(/\.[^.]+$/, "");
|
|
99
|
+
const toWithoutExt = toBasename.replace(/\.[^.]+$/, "");
|
|
100
|
+
const importPattern1 = new RegExp(`(['"\`])([./]*(?:[^'"\`]*/)?)${escapeRegex(fromWithoutExt)}\\1`, "g");
|
|
101
|
+
updated = updated.replace(importPattern1, (_match, quote, pathPart) => {
|
|
102
|
+
return `${quote}${pathPart}${toWithoutExt}${quote}`;
|
|
103
|
+
});
|
|
104
|
+
const importPattern2 = new RegExp(`(['"\`])([./]*(?:[^'"\`]*/)?)${escapeRegex(fromBasename)}\\1`, "g");
|
|
105
|
+
updated = updated.replace(importPattern2, (_match, quote, pathPart) => {
|
|
106
|
+
return `${quote}${pathPart}${toBasename}${quote}`;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (updated !== content) {
|
|
110
|
+
await fs.writeFile(filePath, updated, "utf-8");
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function escapeRegex(str) {
|
|
116
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
117
|
+
}
|
|
118
|
+
async function isTextFile(filePath) {
|
|
119
|
+
try {
|
|
120
|
+
const fd = await fs.open(filePath, "r");
|
|
121
|
+
const buffer = Buffer.alloc(512);
|
|
122
|
+
const { bytesRead } = await fd.read(buffer, 0, 512, 0);
|
|
123
|
+
await fd.close();
|
|
124
|
+
if (bytesRead === 0)
|
|
125
|
+
return true;
|
|
126
|
+
for (let i = 0;i < bytesRead; i++) {
|
|
127
|
+
if (buffer[i] === 0)
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
let printableCount = 0;
|
|
131
|
+
for (let i = 0;i < bytesRead; i++) {
|
|
132
|
+
const byte = buffer[i];
|
|
133
|
+
if (byte >= 32 && byte <= 126 || byte === 9 || byte === 10 || byte === 13) {
|
|
134
|
+
printableCount++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return printableCount / bytesRead > 0.85;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
90
142
|
async function renameRecursive(root, style = "title_underscore", options = {}) {
|
|
91
143
|
const performed = [];
|
|
144
|
+
const updateContentEnabled = options.updateContent ?? true;
|
|
92
145
|
async function walk(current) {
|
|
93
146
|
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
94
147
|
for (const e of entries) {
|
|
@@ -126,6 +179,28 @@ async function renameRecursive(root, style = "title_underscore", options = {}) {
|
|
|
126
179
|
}
|
|
127
180
|
}
|
|
128
181
|
await walk(root);
|
|
182
|
+
if (!options.dryRun && updateContentEnabled && performed.length > 0) {
|
|
183
|
+
const filesToUpdate = [];
|
|
184
|
+
async function collectFiles(dir) {
|
|
185
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
186
|
+
for (const e of entries) {
|
|
187
|
+
const full = path.join(dir, e.name);
|
|
188
|
+
if (e.isFile()) {
|
|
189
|
+
filesToUpdate.push(full);
|
|
190
|
+
} else if (e.isDirectory()) {
|
|
191
|
+
await collectFiles(full);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
await collectFiles(root);
|
|
196
|
+
for (const file of filesToUpdate) {
|
|
197
|
+
try {
|
|
198
|
+
if (await isTextFile(file)) {
|
|
199
|
+
await updateFileReferences(file, performed);
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
129
204
|
return performed;
|
|
130
205
|
}
|
|
131
206
|
if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
|
|
@@ -133,17 +208,21 @@ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
|
|
|
133
208
|
const rootArg = args[0];
|
|
134
209
|
const styleArg = args[1];
|
|
135
210
|
if (args.includes("--help") || args.includes("-h") || args.includes("/help") || args.includes("/h") || args.includes("/?")) {
|
|
136
|
-
console.log("Usage: rename <root> <style> [--dry|-n]");
|
|
211
|
+
console.log("Usage: rename <root> <style> [--dry|-n] [--no-update-content]");
|
|
137
212
|
console.log("Styles: title_underscore, pascal_underscore, snake, kebab, camel, pascal, upper, lower");
|
|
213
|
+
console.log("Options:");
|
|
214
|
+
console.log(" --dry, -n Preview changes without applying them");
|
|
215
|
+
console.log(" --no-update-content Skip updating import/require statements in files");
|
|
138
216
|
process.exit(0);
|
|
139
217
|
}
|
|
140
218
|
const dryRun = args.includes("--dry") || args.includes("-n");
|
|
219
|
+
const updateContent = !args.includes("--no-update-content");
|
|
141
220
|
if (!rootArg || !styleArg) {
|
|
142
|
-
console.error("Usage: rename <root> <style> [--dry|-n]");
|
|
221
|
+
console.error("Usage: rename <root> <style> [--dry|-n] [--no-update-content]");
|
|
143
222
|
console.error("Styles: title_underscore, pascal_underscore, snake, kebab, camel, pascal, upper, lower");
|
|
144
223
|
process.exit(1);
|
|
145
224
|
}
|
|
146
|
-
renameRecursive(rootArg, styleArg, { dryRun }).then((performed) => {
|
|
225
|
+
renameRecursive(rootArg, styleArg, { dryRun, updateContent }).then((performed) => {
|
|
147
226
|
if (dryRun) {
|
|
148
227
|
console.log(`Dry run - would rename ${performed.length} items:`);
|
|
149
228
|
} else {
|
package/bin/commands/git/fodd.sh
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Safely switch to a branch and delete the previous one (local & remote)
|
|
3
|
+
|
|
4
|
+
# Git switch and clean: Safely switch to a branch and delete the old one
|
|
5
|
+
# Usage: switch-clean <target-branch>
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
# Get target branch from argument
|
|
10
|
+
TARGET_BRANCH="$1"
|
|
11
|
+
|
|
12
|
+
if [ -z "$TARGET_BRANCH" ]; then
|
|
13
|
+
echo "Error: Target branch is required"
|
|
14
|
+
echo "Usage: switch-clean <target-branch>"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Get current branch
|
|
19
|
+
CURRENT_BRANCH=$(git branch --show-current)
|
|
20
|
+
|
|
21
|
+
if [ -z "$CURRENT_BRANCH" ]; then
|
|
22
|
+
echo "Error: Not on a branch (detached HEAD)"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Check if already on target branch
|
|
27
|
+
if [ "$CURRENT_BRANCH" = "$TARGET_BRANCH" ]; then
|
|
28
|
+
echo "Error: Already on branch '$TARGET_BRANCH'"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check for uncommitted changes
|
|
33
|
+
if ! git diff-index --quiet HEAD --; then
|
|
34
|
+
echo "Error: You have uncommitted changes. Please commit or stash them first."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Check if current branch has unpushed commits
|
|
39
|
+
UNPUSHED=$(git log @{u}.. --oneline 2>/dev/null || echo "")
|
|
40
|
+
if [ -n "$UNPUSHED" ]; then
|
|
41
|
+
echo "Error: Current branch '$CURRENT_BRANCH' has unpushed commits:"
|
|
42
|
+
echo "$UNPUSHED"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Fetch target branch from origin
|
|
47
|
+
echo "Fetching $TARGET_BRANCH from origin..."
|
|
48
|
+
git fetch origin "$TARGET_BRANCH:$TARGET_BRANCH" 2>/dev/null || {
|
|
49
|
+
echo "Error: Failed to fetch branch '$TARGET_BRANCH' from origin"
|
|
50
|
+
exit 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Checkout to target branch
|
|
54
|
+
echo "Switching to $TARGET_BRANCH..."
|
|
55
|
+
git checkout "$TARGET_BRANCH"
|
|
56
|
+
|
|
57
|
+
# Delete local branch
|
|
58
|
+
echo "Deleting local branch $CURRENT_BRANCH..."
|
|
59
|
+
git branch -d "$CURRENT_BRANCH"
|
|
60
|
+
|
|
61
|
+
# Delete remote branch if it exists
|
|
62
|
+
if git ls-remote --exit-code --heads origin "$CURRENT_BRANCH" > /dev/null 2>&1; then
|
|
63
|
+
echo "Deleting remote branch $CURRENT_BRANCH..."
|
|
64
|
+
git push origin --delete "$CURRENT_BRANCH"
|
|
65
|
+
echo "✓ Switched to '$TARGET_BRANCH' and deleted '$CURRENT_BRANCH' (local & remote)"
|
|
66
|
+
else
|
|
67
|
+
echo "✓ Switched to '$TARGET_BRANCH' and deleted '$CURRENT_BRANCH' (local only)"
|
|
68
|
+
fi
|
package/bin/commands/nvm/lts.sh
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Initialize SSH directory with proper permissions
|
|
3
|
+
|
|
4
|
+
# Create .ssh directory if it doesn't exist
|
|
5
|
+
mkdir -p ~/.ssh
|
|
6
|
+
|
|
7
|
+
# Create files if they don't exist
|
|
2
8
|
touch ~/.ssh/known_hosts
|
|
3
9
|
touch ~/.ssh/config
|
|
10
|
+
|
|
11
|
+
# Set permissions (works on Unix/Linux/macOS and Git Bash on Windows)
|
|
4
12
|
chmod 700 ~/.ssh
|
|
5
13
|
chmod 644 ~/.ssh/known_hosts
|
|
6
14
|
chmod 644 ~/.ssh/config
|
|
15
|
+
|
|
16
|
+
echo "✓ SSH directory initialized at ~/.ssh"
|
package/bin/index.js
CHANGED