@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 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 {
@@ -1 +1,4 @@
1
+ #!/bin/bash
2
+ # @description Fetch and update develop branch from origin
3
+
1
4
  git fetch origin develop:develop
@@ -1,3 +1,6 @@
1
+ #!/bin/bash
2
+ # @description Run build and stage bin/ directory before commit
3
+
1
4
  echo "Running build before commit..."
2
5
  bun run build
3
6
  git add bin/
@@ -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
@@ -1 +1,4 @@
1
+ #!/bin/bash
2
+ # @description Print a personalized hello world message
3
+
1
4
  echo "Hello World, {{name}}"
@@ -1 +1,4 @@
1
+ #!/bin/bash
2
+ # @description Install Node Version Manager (nvm)
3
+
1
4
  curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
@@ -1,2 +1,5 @@
1
+ #!/bin/bash
2
+ # @description Install and use Node.js LTS version
3
+
1
4
  nvm install --lts
2
5
  nvm use --lts
@@ -1,6 +1,16 @@
1
- mkdir ~/.ssh
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
@@ -23,7 +23,7 @@ async function getVersion() {
23
23
  function detectShell() {
24
24
  const isWindows = os.platform() === "win32";
25
25
  if (isWindows) {
26
- return "powershell";
26
+ return "bash";
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.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Windows and *Nix utility for typical programming activity",
5
5
  "author": "Vitalii Balabanov",
6
6
  "email": "tertiumnon@gmail.com",