@tertium/hlpr 0.3.0 → 0.3.3

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.
@@ -0,0 +1,157 @@
1
+ // src/commands/file/rename/rename.ts
2
+ import fs from "fs/promises";
3
+ import path from "path";
4
+ function splitWords(s) {
5
+ const parts = s.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[^\p{L}\p{N}]+/u).map((p) => p.trim()).filter(Boolean);
6
+ return parts;
7
+ }
8
+ function transformBasename(basename, style) {
9
+ if (!basename)
10
+ return basename;
11
+ if (basename.includes(".") && !basename.startsWith("."))
12
+ return basename;
13
+ const leadingDot = basename.startsWith(".") ? "." : "";
14
+ const core = leadingDot ? basename.slice(1) : basename;
15
+ const words = splitWords(core);
16
+ if (words.length === 0)
17
+ return basename;
18
+ switch (style) {
19
+ case "title_underscore":
20
+ return leadingDot + words.map(cap).join("_");
21
+ case "snake":
22
+ return leadingDot + words.map((w) => w.toLowerCase()).join("_");
23
+ case "kebab":
24
+ return leadingDot + words.map((w) => w.toLowerCase()).join("-");
25
+ case "camel":
26
+ return leadingDot + words.map((w, i) => i === 0 ? w.toLowerCase() : cap(w)).join("");
27
+ case "pascal":
28
+ return leadingDot + words.map(cap).join("");
29
+ case "upper":
30
+ return leadingDot + words.join("_").toUpperCase();
31
+ case "lower":
32
+ return leadingDot + words.join("_").toLowerCase();
33
+ default:
34
+ return basename;
35
+ }
36
+ }
37
+ function cap(s) {
38
+ if (!s)
39
+ return s;
40
+ return s[0].toUpperCase() + s.slice(1).toLowerCase();
41
+ }
42
+ async function exists(p) {
43
+ try {
44
+ await fs.access(p);
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+ async function uniqueDestination(dest) {
51
+ if (!await exists(dest))
52
+ return dest;
53
+ const dir = path.dirname(dest);
54
+ const parsed = path.parse(dest);
55
+ let i = 1;
56
+ while (true) {
57
+ const candidate = path.join(dir, `${parsed.name}_${i}${parsed.ext}`);
58
+ if (!await exists(candidate))
59
+ return candidate;
60
+ i++;
61
+ }
62
+ }
63
+ async function safeRename(oldPath, newPath) {
64
+ const oldLower = oldPath.toLowerCase();
65
+ const newLower = newPath.toLowerCase();
66
+ if (oldLower === newLower && oldPath !== newPath) {
67
+ const tmp = newPath + "__tmp_renaming__";
68
+ await fs.rename(oldPath, tmp);
69
+ try {
70
+ await fs.rename(tmp, newPath);
71
+ } catch (err) {
72
+ await fs.rename(tmp, oldPath).catch(() => {});
73
+ throw err;
74
+ }
75
+ } else {
76
+ const final = await uniqueDestination(newPath);
77
+ if (final !== newPath) {
78
+ await fs.rename(oldPath, final);
79
+ } else {
80
+ await fs.rename(oldPath, newPath);
81
+ }
82
+ }
83
+ }
84
+ async function renameRecursive(root, style = "title_underscore", options = {}) {
85
+ const performed = [];
86
+ async function walk(current) {
87
+ const entries = await fs.readdir(current, { withFileTypes: true });
88
+ for (const e of entries) {
89
+ const full = path.join(current, e.name);
90
+ if (e.isFile()) {
91
+ const parsed = path.parse(e.name);
92
+ const newBase = transformBasename(parsed.name, style) + parsed.ext;
93
+ if (newBase !== e.name) {
94
+ const dest = path.join(current, newBase);
95
+ if (options.dryRun) {
96
+ performed.push({ from: full, to: dest });
97
+ } else {
98
+ await safeRename(full, dest);
99
+ performed.push({ from: full, to: dest });
100
+ }
101
+ }
102
+ }
103
+ }
104
+ const entries2 = await fs.readdir(current, { withFileTypes: true });
105
+ for (const e of entries2) {
106
+ const full = path.join(current, e.name);
107
+ if (e.isDirectory()) {
108
+ await walk(full);
109
+ const newBase = transformBasename(e.name, style);
110
+ if (newBase !== e.name) {
111
+ const dest = path.join(current, newBase);
112
+ if (options.dryRun) {
113
+ performed.push({ from: full, to: dest });
114
+ } else {
115
+ await safeRename(full, dest);
116
+ performed.push({ from: full, to: dest });
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ await walk(root);
123
+ return performed;
124
+ }
125
+ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
126
+ const args = process.argv.slice(2);
127
+ const rootArg = args[0];
128
+ const styleArg = args[1];
129
+ const dryRun = args.includes("--dry") || args.includes("-n");
130
+ if (!rootArg || !styleArg) {
131
+ console.error("Usage: rename <root> <style> [--dry|-n]");
132
+ console.error("Styles: title_underscore, snake, kebab, camel, pascal, upper, lower");
133
+ process.exit(1);
134
+ }
135
+ renameRecursive(rootArg, styleArg, { dryRun }).then((performed) => {
136
+ if (dryRun) {
137
+ console.log(`Dry run - would rename ${performed.length} items:`);
138
+ } else {
139
+ console.log(`Renamed ${performed.length} items:`);
140
+ }
141
+ performed.forEach(({ from, to }) => {
142
+ console.log(` ${from} → ${to}`);
143
+ });
144
+ }).catch((err) => {
145
+ console.error("Error:", err);
146
+ process.exit(1);
147
+ });
148
+ }
149
+ var rename_default = {
150
+ transformBasename,
151
+ renameRecursive
152
+ };
153
+ export {
154
+ transformBasename,
155
+ renameRecursive,
156
+ rename_default as default
157
+ };
@@ -0,0 +1 @@
1
+ git fetch origin develop:develop
@@ -0,0 +1,3 @@
1
+ echo "Running build before commit..."
2
+ bun run build
3
+ git add bin/
@@ -0,0 +1 @@
1
+ echo "Hello World, {{name}}"
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/commands/help/help.ts
5
+ import * as path from "path";
6
+ import * as fs from "fs";
7
+ import { fileURLToPath } from "url";
8
+ import { readFile } from "fs/promises";
9
+ async function getVersion() {
10
+ try {
11
+ const __filename2 = fileURLToPath(import.meta.url);
12
+ const __dirname2 = path.dirname(__filename2);
13
+ const packagePath = path.join(__dirname2, "..", "..", "package.json");
14
+ const packageJson = JSON.parse(await readFile(packagePath, "utf-8"));
15
+ return packageJson.version;
16
+ } catch (error) {
17
+ return "0.0.0";
18
+ }
19
+ }
20
+ async function getCommandDescription(filePath) {
21
+ try {
22
+ const content = await readFile(filePath, "utf-8");
23
+ const descMatch = content.match(/(?:\/\/|#)\s*@description\s+(.+)/);
24
+ if (descMatch) {
25
+ return descMatch[1].trim();
26
+ }
27
+ const dir = path.dirname(filePath);
28
+ const readmePath = path.join(dir, "README.md");
29
+ if (fs.existsSync(readmePath)) {
30
+ const readme = await readFile(readmePath, "utf-8");
31
+ const firstLine = readme.split(`
32
+ `).find((line) => line.trim() && !line.startsWith("#"));
33
+ if (firstLine) {
34
+ return firstLine.trim();
35
+ }
36
+ }
37
+ } catch (error) {}
38
+ return;
39
+ }
40
+ async function discoverCommands(commandsDir) {
41
+ const commands = [];
42
+ try {
43
+ const categories = await fs.promises.readdir(commandsDir, { withFileTypes: true });
44
+ for (const category of categories) {
45
+ if (!category.isDirectory())
46
+ continue;
47
+ const categoryPath = path.join(commandsDir, category.name);
48
+ const items = await fs.promises.readdir(categoryPath, { withFileTypes: true });
49
+ for (const item of items) {
50
+ if (item.isDirectory()) {
51
+ const nestedJsPath = path.join(categoryPath, item.name, `${item.name}.js`);
52
+ const nestedShPath = path.join(categoryPath, item.name, `${item.name}.sh`);
53
+ let commandPath;
54
+ let commandType;
55
+ if (fs.existsSync(nestedJsPath)) {
56
+ commandPath = nestedJsPath;
57
+ commandType = "typescript";
58
+ } else if (fs.existsSync(nestedShPath)) {
59
+ commandPath = nestedShPath;
60
+ commandType = "shell";
61
+ } else {
62
+ continue;
63
+ }
64
+ const description = await getCommandDescription(commandPath);
65
+ commands.push({
66
+ category: category.name,
67
+ name: item.name,
68
+ type: commandType,
69
+ path: commandPath,
70
+ description
71
+ });
72
+ } else if (item.name.endsWith(".js")) {
73
+ const jsPath = path.join(categoryPath, item.name);
74
+ const commandName = path.basename(item.name, ".js");
75
+ if (commandName !== "test" && !commandName.endsWith(".test")) {
76
+ const description = await getCommandDescription(jsPath);
77
+ commands.push({
78
+ category: category.name,
79
+ name: commandName,
80
+ type: "typescript",
81
+ path: jsPath,
82
+ description
83
+ });
84
+ }
85
+ } else if (item.name.endsWith(".sh")) {
86
+ const shPath = path.join(categoryPath, item.name);
87
+ const commandName = path.basename(item.name, ".sh");
88
+ const description = await getCommandDescription(shPath);
89
+ commands.push({
90
+ category: category.name,
91
+ name: commandName,
92
+ type: "shell",
93
+ path: shPath,
94
+ description
95
+ });
96
+ }
97
+ }
98
+ }
99
+ } catch (error) {
100
+ console.error("Error discovering commands:", error);
101
+ }
102
+ return commands;
103
+ }
104
+ function printHelp(commands, version) {
105
+ console.log(`
106
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
107
+ console.log(`\u2551 hlpr - Helper CLI Tool v${version.padEnd(16)}\u2551`);
108
+ console.log(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
109
+ `);
110
+ console.log("USAGE:");
111
+ console.log(` hlpr [options] <category> <command> [args...]
112
+ `);
113
+ console.log("OPTIONS:");
114
+ console.log(" -f Force execution (continue on errors)");
115
+ console.log(" -v, --version Show version information");
116
+ console.log(" help, -h, --help, /h, /help, /?");
117
+ console.log(` Show this help message
118
+ `);
119
+ console.log(`AVAILABLE COMMANDS:
120
+ `);
121
+ const grouped = commands.reduce((acc, cmd) => {
122
+ if (!acc[cmd.category]) {
123
+ acc[cmd.category] = [];
124
+ }
125
+ acc[cmd.category].push(cmd);
126
+ return acc;
127
+ }, {});
128
+ const sortedCategories = Object.keys(grouped).sort();
129
+ for (const category of sortedCategories) {
130
+ console.log(` ${category}:`);
131
+ const categoryCommands = grouped[category].sort((a, b) => a.name.localeCompare(b.name));
132
+ for (const cmd of categoryCommands) {
133
+ const typeLabel = cmd.type === "typescript" ? "(TS)" : "(sh)";
134
+ const cmdDisplay = cmd.category === cmd.name ? `hlpr ${cmd.name}`.padEnd(35) : `hlpr ${cmd.category} ${cmd.name}`.padEnd(35);
135
+ if (cmd.description) {
136
+ console.log(` ${cmdDisplay} ${typeLabel.padEnd(6)} ${cmd.description}`);
137
+ } else {
138
+ console.log(` ${cmdDisplay} ${typeLabel}`);
139
+ }
140
+ }
141
+ console.log("");
142
+ }
143
+ console.log("EXAMPLES:");
144
+ console.log(" hlpr help");
145
+ console.log(" hlpr file rename --style kebab --dir ./src");
146
+ console.log(" hlpr ssh init-dir");
147
+ console.log(` hlpr -f git precommit
148
+ `);
149
+ }
150
+ async function main() {
151
+ try {
152
+ const __filename2 = fileURLToPath(import.meta.url);
153
+ const __dirname2 = path.dirname(__filename2);
154
+ const binCommandsDir = path.join(__dirname2, "..");
155
+ const srcCommandsDir = path.join(__dirname2, "..", "..", "..", "src", "commands");
156
+ const version = await getVersion();
157
+ const commands = [
158
+ ...await discoverCommands(binCommandsDir),
159
+ ...await discoverCommands(srcCommandsDir)
160
+ ];
161
+ const uniqueCommands = commands.filter((cmd, index, self) => index === self.findIndex((c) => c.category === cmd.category && c.name === cmd.name));
162
+ printHelp(uniqueCommands, version);
163
+ } catch (error) {
164
+ console.error("Error:", error);
165
+ process.exit(1);
166
+ }
167
+ }
168
+ main();
@@ -0,0 +1 @@
1
+ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
@@ -0,0 +1,2 @@
1
+ nvm install --lts
2
+ nvm use --lts
@@ -0,0 +1,6 @@
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
@@ -25,6 +25,9 @@ function detectShell() {
25
25
  }
26
26
  return "bash";
27
27
  }
28
+ var __filename2 = fileURLToPath(import.meta.url);
29
+ var __dirname2 = path.dirname(__filename2);
30
+ var scriptDir = path.join(__dirname2, "..", "src");
28
31
  var args = process.argv.slice(2);
29
32
  var forceFlag = false;
30
33
  var commandArgs = [];
@@ -80,11 +83,9 @@ async function main() {
80
83
  process.exit(0);
81
84
  }
82
85
  if (commandArgs.length === 0 || commandArgs[0] === "help" || commandArgs[0] === "--help" || commandArgs[0] === "-h" || commandArgs[0] === "/help" || commandArgs[0] === "/h" || commandArgs[0] === "/?") {
83
- const __filename2 = fileURLToPath(import.meta.url);
84
- const __dirname2 = path.dirname(__filename2);
85
- const helpScriptPath = path.join(__dirname2, "commands", "help", "help.ts");
86
+ const helpScriptPath = path.join(__dirname2, "..", "bin", "commands", "help", "help.js");
86
87
  if (fs.existsSync(helpScriptPath)) {
87
- const helpCommand = `bun "${helpScriptPath}"`;
88
+ const helpCommand = `node "${helpScriptPath}"`;
88
89
  await executeCommand(helpCommand, {});
89
90
  rl.close();
90
91
  process.exit(0);
@@ -97,31 +98,33 @@ async function main() {
97
98
  try {
98
99
  const category = commandArgs[0];
99
100
  const restArgs = commandArgs.slice(1);
100
- const __filename2 = fileURLToPath(import.meta.url);
101
- const __dirname2 = path.dirname(__filename2);
102
- const scriptDir = __dirname2;
103
101
  let scriptPath;
104
102
  let isTypeScriptCommand = false;
105
103
  if (restArgs.length > 0) {
106
104
  const subcategory = restArgs[0];
107
- const nestedTsPath = path.join(scriptDir, "commands", category, subcategory, `${subcategory}.ts`);
108
- if (fs.existsSync(nestedTsPath)) {
109
- scriptPath = nestedTsPath;
105
+ const nestedJsPath = path.join(__dirname2, "..", "bin", "commands", category, subcategory, `${subcategory}.js`);
106
+ if (fs.existsSync(nestedJsPath)) {
107
+ scriptPath = nestedJsPath;
110
108
  isTypeScriptCommand = true;
111
109
  }
112
110
  }
113
111
  if (!isTypeScriptCommand) {
114
- const tsPath = path.join(scriptDir, "commands", category, `${category}.ts`);
115
- if (fs.existsSync(tsPath)) {
116
- scriptPath = tsPath;
112
+ const jsPath = path.join(__dirname2, "..", "bin", "commands", category, `${category}.js`);
113
+ if (fs.existsSync(jsPath)) {
114
+ scriptPath = jsPath;
117
115
  isTypeScriptCommand = true;
118
116
  }
119
117
  }
120
118
  if (!isTypeScriptCommand && restArgs.length > 0) {
121
119
  const scriptName = restArgs.join("");
122
- scriptPath = path.join(scriptDir, "commands", category, `${scriptName}.sh`);
123
- if (!fs.existsSync(scriptPath)) {
124
- console.error(`Script not found: ${scriptPath}`);
120
+ let scriptPathCandidate = path.join(scriptDir, "commands", category, `${scriptName}.sh`);
121
+ if (!fs.existsSync(scriptPathCandidate)) {
122
+ scriptPathCandidate = path.join(__dirname2, "..", "bin", "commands", category, `${scriptName}.sh`);
123
+ }
124
+ if (fs.existsSync(scriptPathCandidate)) {
125
+ scriptPath = scriptPathCandidate;
126
+ } else {
127
+ console.error(`Script not found: ${scriptPathCandidate}`);
125
128
  process.exit(1);
126
129
  }
127
130
  }
@@ -131,8 +134,8 @@ async function main() {
131
134
  }
132
135
  if (isTypeScriptCommand) {
133
136
  const tsArgs = restArgs.length > 0 && restArgs[0] && fs.existsSync(path.join(scriptDir, "commands", category, restArgs[0])) ? process.argv.slice(4) : process.argv.slice(3);
134
- const finalCommand = `bun "${scriptPath}" ${tsArgs.join(" ")}`;
135
- console.log(`Executing TypeScript command: ${finalCommand}`);
137
+ const finalCommand = `node "${scriptPath}" ${tsArgs.join(" ")}`;
138
+ console.log(`Executing command: ${finalCommand}`);
136
139
  const success2 = await executeCommand(finalCommand, {});
137
140
  if (!success2 && !forceFlag) {
138
141
  console.error("Command failed, stopping execution.");
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@tertium/hlpr",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "Windows and *Nix utility for typical programming activity",
5
5
  "author": "Vitalii Balabanov",
6
6
  "email": "tertiumnon@gmail.com",
7
- "main": "index.js",
7
+ "main": "bin/index.js",
8
8
  "type": "module",
9
9
  "scripts": {
10
- "build": "bun build ./src/index.ts --outfile ./index.js --target node",
10
+ "build:main": "bun build ./src/index.ts --outfile ./bin/index.js --target node",
11
+ "build:commands": "node build-commands.js",
12
+ "build": "bun run build:main && bun run build:commands",
13
+ "test": "bun test",
11
14
  "prepublishOnly": "npm run build",
12
15
  "release:minor": "node node_modules/@tertium/js/scripts/release.js minor",
13
16
  "release:patch": "node node_modules/@tertium/js/scripts/release.js patch",
@@ -21,11 +24,10 @@
21
24
  "typescript": "^5.8.3"
22
25
  },
23
26
  "bin": {
24
- "hlpr": "./bin/hlpr.js"
27
+ "hlpr": "./bin/index.js"
25
28
  },
26
29
  "files": [
27
30
  "bin",
28
- "index.js",
29
31
  "package.json"
30
32
  ],
31
33
  "keywords": [
package/bin/hlpr.js DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { main } from '../index.js';
4
- main();