@tertium/hlpr 0.1.15 → 0.2.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
@@ -1,6 +1,6 @@
1
1
  # hlpr
2
2
 
3
- A CLI utility for running shell scripts with variable substitution.
3
+ A CLI utility for running shell scripts with variable substitution and TypeScript-based commands.
4
4
 
5
5
  ## Installation
6
6
 
@@ -26,20 +26,87 @@ hlpr git fodd
26
26
  hlpr hello world
27
27
  hlpr ssh init dir
28
28
 
29
+ # TypeScript commands (nested structure)
30
+ hlpr file rename <directory> <style> [--dry|-n]
31
+
29
32
  # Continue execution even if commands fail
30
33
  hlpr -f ssh init dir
31
34
  ```
32
35
 
36
+ ## Available Commands
37
+
38
+ ### Shell Script Commands
39
+
40
+ Shell scripts support variable substitution using `{{variable}}` syntax.
41
+
42
+ - `git fodd` - Git command
43
+ - `hello world` - Hello world example
44
+ - `ssh init dir` - SSH initialization
45
+
46
+ ### TypeScript Commands
47
+
48
+ TypeScript commands are implemented as `.ts` files and run directly with Bun.
49
+
50
+ #### file rename
51
+
52
+ Recursively rename files and folders according to a specific case style.
53
+
54
+ **Supported styles:**
55
+
56
+ - `title_underscore` - Title_Case_With_Underscores
57
+ - `snake` - snake_case_lowercase
58
+ - `kebab` - kebab-case-lowercase
59
+ - `camel` - camelCaseLowerFirst
60
+ - `pascal` - PascalCaseUpperFirst
61
+ - `upper` - UPPER_CASE_WITH_UNDERSCORES
62
+ - `lower` - lower_case_with_underscores
63
+
64
+ **Examples:**
65
+
66
+ ```bash
67
+ # Dry run (preview changes without applying)
68
+ hlpr file rename ./my-project kebab --dry
69
+ hlpr file rename ./src snake -n
70
+
71
+ # Apply changes
72
+ hlpr file rename ./my-project kebab
73
+ hlpr file rename ./docs pascal
74
+ ```
75
+
76
+ **Features:**
77
+
78
+ - ✅ Recursive directory traversal
79
+ - ✅ Extension preservation (e.g., `file-name.md` core name only)
80
+ - ✅ Leading dot file support (e.g., `.gitignore`)
81
+ - ✅ Case-only rename handling (Windows compatibility)
82
+ - ✅ Conflict resolution (adds `_N` suffix)
83
+ - ✅ Dry-run mode (`--dry` or `-n`)
84
+
85
+ See `commands/file/rename/README.md` for detailed documentation.
86
+
33
87
  ## How It Works
34
88
 
89
+ ### Shell Scripts
90
+
35
91
  1. The command `hlpr hello world` looks for a script at `commands/hello/world.sh`
36
92
  2. If the script contains variables like `{{name}}`, you'll be prompted to enter values
37
93
  3. Each line of the script is executed with the variables replaced
38
94
 
95
+ ### TypeScript Commands
96
+
97
+ 1. The command `hlpr file rename` looks for a script at `commands/file/rename/rename.ts`
98
+ 2. Arguments are passed directly to the TypeScript module
99
+ 3. The module is executed with Bun runtime
100
+
39
101
  ## Directory Structure
40
102
 
41
103
  ```
42
104
  commands/
105
+ ├── file/
106
+ │ └── rename/
107
+ │ ├── rename.ts
108
+ │ ├── rename.test.ts
109
+ │ └── README.md
43
110
  ├── git/
44
111
  │ └── fodd.sh
45
112
  ├── hello/
@@ -53,22 +120,50 @@ commands/
53
120
 
54
121
  ## Adding Your Own Scripts
55
122
 
123
+ ### Shell Scripts
124
+
56
125
  1. Create a directory structure in `commands/<category>/`
57
126
  2. Add your `.sh` script files
58
127
  3. Use `{{variable}}` syntax for user inputs
59
128
 
60
129
  Example script (`commands/hello/world.sh`):
130
+
61
131
  ```bash
62
132
  echo "Hello World, {{name}}"
63
133
  ```
64
134
 
135
+ ### TypeScript Commands
136
+
137
+ 1. Create a nested directory structure in `commands/<category>/<command>/`
138
+ 2. Add your TypeScript file named `<command>.ts`
139
+ 3. Implement CLI argument parsing and logic
140
+ 4. Optionally add tests in `<command>.test.ts`
141
+
142
+ Example structure:
143
+
144
+ ```text
145
+ commands/
146
+ └── file/
147
+ └── rename/
148
+ ├── rename.ts # Main implementation
149
+ ├── rename.test.ts # Tests
150
+ └── README.md # Documentation
151
+ ```
152
+
65
153
  ## Command Naming
66
154
 
67
155
  The utility maps command arguments to script files:
156
+
157
+ **Shell Scripts:**
158
+
68
159
  - `hlpr git fodd` → runs `commands/git/fodd.sh`
69
160
  - `hlpr hello world` → runs `commands/hello/world.sh`
70
161
  - `hlpr ssh init dir` → runs `commands/ssh/initdir.sh`
71
162
 
163
+ **TypeScript Commands:**
164
+
165
+ - `hlpr file rename <args>` → runs `commands/file/rename/rename.ts`
166
+
72
167
  ## Error Handling
73
168
 
74
169
  By default, the utility stops execution if any command fails. Use the `-f` flag to continue execution despite failures:
@@ -99,4 +194,4 @@ npm link
99
194
 
100
195
  ## License
101
196
 
102
- MIT
197
+ MIT
package/index.js CHANGED
@@ -1,6 +1,3 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
1
  // src/index.ts
5
2
  import { readFile } from "node:fs/promises";
6
3
  import { exec } from "node:child_process";
@@ -8,10 +5,12 @@ import * as path from "node:path";
8
5
  import * as fs from "node:fs";
9
6
  import * as readline from "node:readline";
10
7
  import * as os from "node:os";
11
- var __dirname = "/home/runner/work/tertium-hlpr/tertium-hlpr/src";
8
+ import { fileURLToPath } from "node:url";
12
9
  async function getVersion() {
13
10
  try {
14
- const packagePath = path.join(path.dirname(path.dirname(__dirname)), "package.json");
11
+ const __filename2 = fileURLToPath(import.meta.url);
12
+ const __dirname2 = path.dirname(__filename2);
13
+ const packagePath = path.join(path.dirname(__dirname2), "package.json");
15
14
  const packageJson = JSON.parse(await readFile(packagePath, "utf-8"));
16
15
  return packageJson.version;
17
16
  } catch (error) {
@@ -87,13 +86,51 @@ async function main() {
87
86
  try {
88
87
  const category = commandArgs[0];
89
88
  const restArgs = commandArgs.slice(1);
90
- const scriptName = restArgs.join("");
91
- const scriptDir = path.dirname(path.dirname(__dirname));
92
- const scriptPath = path.join(scriptDir, "commands", category, `${scriptName}.sh`);
93
- if (!fs.existsSync(scriptPath)) {
94
- console.error(`Script not found: ${scriptPath}`);
89
+ const __filename2 = fileURLToPath(import.meta.url);
90
+ const __dirname2 = path.dirname(__filename2);
91
+ const scriptDir = __dirname2;
92
+ let scriptPath;
93
+ let isTypeScriptCommand = false;
94
+ if (restArgs.length > 0) {
95
+ const subcategory = restArgs[0];
96
+ const nestedTsPath = path.join(scriptDir, "commands", category, subcategory, `${subcategory}.ts`);
97
+ if (fs.existsSync(nestedTsPath)) {
98
+ scriptPath = nestedTsPath;
99
+ isTypeScriptCommand = true;
100
+ }
101
+ }
102
+ if (!isTypeScriptCommand) {
103
+ const tsPath = path.join(scriptDir, "commands", category, `${category}.ts`);
104
+ if (fs.existsSync(tsPath)) {
105
+ scriptPath = tsPath;
106
+ isTypeScriptCommand = true;
107
+ }
108
+ }
109
+ if (!isTypeScriptCommand && restArgs.length > 0) {
110
+ const scriptName = restArgs.join("");
111
+ scriptPath = path.join(scriptDir, "commands", category, `${scriptName}.sh`);
112
+ if (!fs.existsSync(scriptPath)) {
113
+ console.error(`Script not found: ${scriptPath}`);
114
+ process.exit(1);
115
+ }
116
+ }
117
+ if (!scriptPath) {
118
+ console.error(`Command not found. Usage: hlpr ${category} <args>`);
95
119
  process.exit(1);
96
120
  }
121
+ if (isTypeScriptCommand) {
122
+ const tsArgs = restArgs.length > 0 && restArgs[0] && fs.existsSync(path.join(scriptDir, "commands", category, restArgs[0])) ? process.argv.slice(4) : process.argv.slice(3);
123
+ const finalCommand = `bun "${scriptPath}" ${tsArgs.join(" ")}`;
124
+ console.log(`Executing TypeScript command: ${finalCommand}`);
125
+ const success2 = await executeCommand(finalCommand, {});
126
+ if (!success2 && !forceFlag) {
127
+ console.error("Command failed, stopping execution.");
128
+ process.exit(1);
129
+ }
130
+ console.log("Command completed successfully!");
131
+ rl.close();
132
+ return;
133
+ }
97
134
  const scriptContent = await readFile(scriptPath, "utf-8");
98
135
  const variableRegex = /{{([^}]+)}}/g;
99
136
  const variables = {};
@@ -128,7 +165,7 @@ async function main() {
128
165
  rl.close();
129
166
  }
130
167
  }
131
- if (__require.main == __require.module) {
168
+ if (import.meta.url.startsWith("file:") && process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"))) {
132
169
  main();
133
170
  }
134
171
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tertium/hlpr",
3
- "version": "0.1.15",
3
+ "version": "0.2.0",
4
4
  "description": "Windows and *Nix utility for typical programming activity",
5
5
  "author": "Vitalii Balabanov",
6
6
  "email": "tertiumnon@gmail.com",
@@ -9,13 +9,13 @@
9
9
  "scripts": {
10
10
  "build": "bun build ./src/index.ts --outfile ./index.js --target node",
11
11
  "prepublishOnly": "npm run build",
12
- "release:minor": "node node_modules/@tertium/shared-js-configs-and-types/scripts/release.js minor",
13
- "release:patch": "node node_modules/@tertium/shared-js-configs-and-types/scripts/release.js patch",
14
- "release:major": "node node_modules/@tertium/shared-js-configs-and-types/scripts/release.js major",
12
+ "release:minor": "node node_modules/@tertium/js/scripts/release.js minor",
13
+ "release:patch": "node node_modules/@tertium/js/scripts/release.js patch",
14
+ "release:major": "node node_modules/@tertium/js/scripts/release.js major",
15
15
  "prepare": "husky"
16
16
  },
17
17
  "devDependencies": {
18
- "@tertium/shared-js-configs-and-types": "^0.14.0",
18
+ "@tertium/js": "^0.16.0",
19
19
  "@types/node": "^22.13.10",
20
20
  "husky": "^9.1.7",
21
21
  "typescript": "^5.8.3"
@@ -23,6 +23,11 @@
23
23
  "bin": {
24
24
  "hlpr": "./bin/hlpr.js"
25
25
  },
26
+ "files": [
27
+ "bin",
28
+ "index.js",
29
+ "package.json"
30
+ ],
26
31
  "keywords": [
27
32
  "cli",
28
33
  "utility",
@@ -1,35 +0,0 @@
1
- name: Publish Package
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
- steps:
12
- - uses: actions/checkout@v4
13
-
14
- - name: Install Bun
15
- run: curl -fsSL https://bun.sh/install | bash
16
-
17
- - name: Add Bun to PATH
18
- run: echo "$HOME/.bun/bin" >> $GITHUB_PATH
19
-
20
- - name: Use Node.js
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: "22"
24
- registry-url: "https://registry.npmjs.org/"
25
-
26
- - name: Install dependencies
27
- run: bun install
28
-
29
- - name: Install dependencies
30
- run: bun run build
31
-
32
- - name: Publish to npm (with Bun)
33
- run: npm publish --access public
34
- env:
35
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.husky/pre-commit DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env sh
2
- . "$(dirname -- "$0")/_/husky.sh"
3
-
4
- echo "Running build before commit..."
5
- bun run build
6
- git add bin/hlpr.js
@@ -1,3 +0,0 @@
1
- {
2
- "cSpell.words": ["hlpr"]
3
- }
package/bun.lockb DELETED
Binary file
@@ -1 +0,0 @@
1
- git fetch origin develop:develop
@@ -1,3 +0,0 @@
1
- echo "Running build before commit..."
2
- bun run build
3
- git add bin/
@@ -1 +0,0 @@
1
- echo "Hello World, {{name}}"
@@ -1 +0,0 @@
1
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
@@ -1,2 +0,0 @@
1
- nvm install --lts
2
- nvm use --lts
@@ -1,6 +0,0 @@
1
- mkdir ~/.ssh
2
- touch ~/.ssh/known_hosts
3
- touch ~/.ssh/config
4
- chmod 700 ~/.ssh
5
- chmod 644 ~/.ssh/known_hosts
6
- chmod 644 ~/.ssh/config
package/src/index.ts DELETED
@@ -1,179 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { exec } from "node:child_process";
3
- import * as path from "node:path";
4
- import * as fs from "node:fs";
5
- import * as readline from "node:readline";
6
- import * as os from "node:os";
7
-
8
- // Get package version from package.json
9
- async function getVersion(): Promise<string> {
10
- try {
11
- const packagePath = path.join(path.dirname(path.dirname(__dirname)), 'package.json');
12
- const packageJson = JSON.parse(await readFile(packagePath, 'utf-8'));
13
- return packageJson.version;
14
- } catch (error) {
15
- console.error('Error reading package.json:', error);
16
- return '0.0.0'; // Fallback version
17
- }
18
- }
19
-
20
- // Detect shell type
21
- function detectShell(): string {
22
- const isWindows = os.platform() === "win32";
23
- if (isWindows) {
24
- return "powershell";
25
- }
26
- return "bash"; // Default to bash for Unix-like systems
27
- }
28
-
29
- // Parse command line arguments
30
- const args = process.argv.slice(2);
31
- let forceFlag = false;
32
- let commandArgs: string[] = [];
33
-
34
- // Check for -f flag
35
- if (args[0] === "-f") {
36
- forceFlag = true;
37
- commandArgs = args.slice(1);
38
- } else {
39
- commandArgs = args;
40
- }
41
-
42
- // Create readline interface for user input
43
- const rl = readline.createInterface({
44
- input: process.stdin,
45
- output: process.stdout
46
- });
47
-
48
- // Prompt for user input
49
- function prompt(question: string): Promise<string> {
50
- return new Promise((resolve) => {
51
- rl.question(question, (answer) => {
52
- resolve(answer);
53
- });
54
- });
55
- }
56
-
57
- // Execute a command with variable substitution
58
- async function executeCommand(command: string, variables: Record<string, string>): Promise<boolean> {
59
- // Replace variables in the command
60
- let processedCommand = command;
61
- for (const [key, value] of Object.entries(variables)) {
62
- processedCommand = processedCommand.replace(new RegExp(`{{${key}}}`, 'g'), value);
63
- }
64
-
65
- return new Promise((resolve) => {
66
- console.log(`Executing: ${processedCommand}`);
67
- const childProcess = exec(processedCommand);
68
-
69
- if (childProcess.stdout) {
70
- childProcess.stdout.on("data", (data: string) => {
71
- console.log(data.toString().trim());
72
- });
73
- }
74
-
75
- if (childProcess.stderr) {
76
- childProcess.stderr.on("data", (data: string) => {
77
- console.error(data.toString().trim());
78
- });
79
- }
80
-
81
- childProcess.on("exit", (code: number | null) => {
82
- if (code === 0) {
83
- resolve(true);
84
- } else {
85
- console.error(`Command failed with exit code ${code}`);
86
- resolve(false);
87
- }
88
- });
89
- });
90
- }
91
-
92
- export async function main() {
93
- // Check for version flag
94
- if (commandArgs[0] === "--version" || commandArgs[0] === "-v") {
95
- const version = await getVersion();
96
- console.log(`hlpr version ${version}`);
97
- process.exit(0);
98
- }
99
-
100
- if (commandArgs.length === 0) {
101
- console.error("Please provide a command. Example: hlpr ssh init-dir");
102
- process.exit(1);
103
- }
104
-
105
- try {
106
- // Get the category (first argument) and the rest of the command
107
- const category = commandArgs[0];
108
- const restArgs = commandArgs.slice(1);
109
-
110
- // Join the rest of the arguments without hyphens
111
- const scriptName = restArgs.join("");
112
-
113
- // Get the directory where the hlpr script is installed
114
- const scriptDir = path.dirname(path.dirname(__dirname));
115
- const scriptPath = path.join(scriptDir, "commands", category, `${scriptName}.sh`);
116
-
117
- // Check if the script exists
118
- if (!fs.existsSync(scriptPath)) {
119
- console.error(`Script not found: ${scriptPath}`);
120
- process.exit(1);
121
- }
122
-
123
- // Read the script content
124
- const scriptContent = await readFile(scriptPath, "utf-8");
125
-
126
- // Extract variables from the script (anything in {{variable}})
127
- const variableRegex = /{{([^}]+)}}/g;
128
- const variables: Record<string, string> = {};
129
- const uniqueVars = new Set<string>();
130
-
131
- let match;
132
- while ((match = variableRegex.exec(scriptContent)) !== null) {
133
- uniqueVars.add(match[1]);
134
- }
135
-
136
- // Prompt for each variable
137
- for (const varName of uniqueVars) {
138
- const value = await prompt(`Enter ${varName}: `);
139
- variables[varName] = value;
140
- }
141
-
142
- // Create a temporary script with variables substituted
143
- let processedScript = scriptContent;
144
- for (const [key, value] of Object.entries(variables)) {
145
- processedScript = processedScript.replace(new RegExp(`{{${key}}}`, 'g'), value);
146
- }
147
-
148
- const tempScriptPath = path.join(path.dirname(scriptPath), `_temp_${path.basename(scriptPath)}`);
149
- fs.writeFileSync(tempScriptPath, processedScript);
150
-
151
- // Execute the temporary script with appropriate shell
152
- const shell = detectShell();
153
- const command = shell === "powershell"
154
- ? `powershell -File "${tempScriptPath}"`
155
- : `bash "${tempScriptPath}"`;
156
- const success = await executeCommand(command, {});
157
-
158
- // Clean up the temporary script
159
- fs.unlinkSync(tempScriptPath);
160
-
161
- // If command failed and force flag is not set, exit
162
- if (!success && !forceFlag) {
163
- console.error("Command failed, stopping execution.");
164
- process.exit(1);
165
- }
166
-
167
- console.log("Command completed successfully!");
168
- } catch (error) {
169
- console.error("Error:", error);
170
- process.exit(1);
171
- } finally {
172
- rl.close();
173
- }
174
- }
175
-
176
- // If this file is run directly, execute main
177
- if (require.main === module) {
178
- main();
179
- }
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "CommonJS",
5
- "moduleResolution": "node",
6
- "esModuleInterop": true,
7
- "outDir": "./bin",
8
- "rootDir": "./",
9
- "strict": true,
10
- "declaration": false,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true
13
- },
14
- "include": ["src/**/*"],
15
- "exclude": ["node_modules", "dist"]
16
- }