@tertium/hlpr 0.3.3 → 0.3.5

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.
@@ -8,30 +8,36 @@ function splitWords(s) {
8
8
  function transformBasename(basename, style) {
9
9
  if (!basename)
10
10
  return basename;
11
- if (basename.includes(".") && !basename.startsWith("."))
12
- return basename;
13
11
  const leadingDot = basename.startsWith(".") ? "." : "";
14
- const core = leadingDot ? basename.slice(1) : basename;
12
+ let core = leadingDot ? basename.slice(1) : basename;
13
+ let ext = "";
14
+ const firstDot = core.indexOf(".");
15
+ if (firstDot !== -1) {
16
+ ext = core.slice(firstDot);
17
+ core = core.slice(0, firstDot);
18
+ }
15
19
  const words = splitWords(core);
16
20
  if (words.length === 0)
17
21
  return basename;
18
22
  switch (style) {
19
23
  case "title_underscore":
20
- return leadingDot + words.map(cap).join("_");
24
+ return leadingDot + words.map(cap).join("_") + ext;
21
25
  case "snake":
22
- return leadingDot + words.map((w) => w.toLowerCase()).join("_");
26
+ return leadingDot + words.map((w) => w.toLowerCase()).join("_") + ext;
23
27
  case "kebab":
24
- return leadingDot + words.map((w) => w.toLowerCase()).join("-");
28
+ return leadingDot + words.map((w) => w.toLowerCase()).join("-") + ext;
25
29
  case "camel":
26
- return leadingDot + words.map((w, i) => i === 0 ? w.toLowerCase() : cap(w)).join("");
30
+ return leadingDot + words.map((w, i) => i === 0 ? w.toLowerCase() : cap(w)).join("") + ext;
27
31
  case "pascal":
28
- return leadingDot + words.map(cap).join("");
32
+ return leadingDot + words.map(cap).join("") + ext;
33
+ case "pascal_underscore":
34
+ return leadingDot + words.map(cap).join("_") + ext;
29
35
  case "upper":
30
- return leadingDot + words.join("_").toUpperCase();
36
+ return leadingDot + words.join("_").toUpperCase() + ext;
31
37
  case "lower":
32
- return leadingDot + words.join("_").toLowerCase();
38
+ return leadingDot + words.join("_").toLowerCase() + ext;
33
39
  default:
34
- return basename;
40
+ return leadingDot + core + ext;
35
41
  }
36
42
  }
37
43
  function cap(s) {
@@ -126,10 +132,15 @@ if (import.meta.url.endsWith(process.argv[1]?.replace(/\\/g, "/"))) {
126
132
  const args = process.argv.slice(2);
127
133
  const rootArg = args[0];
128
134
  const styleArg = args[1];
135
+ if (args.includes("--help") || args.includes("-h") || args.includes("/help") || args.includes("/h") || args.includes("/?")) {
136
+ console.log("Usage: rename <root> <style> [--dry|-n]");
137
+ console.log("Styles: title_underscore, pascal_underscore, snake, kebab, camel, pascal, upper, lower");
138
+ process.exit(0);
139
+ }
129
140
  const dryRun = args.includes("--dry") || args.includes("-n");
130
141
  if (!rootArg || !styleArg) {
131
142
  console.error("Usage: rename <root> <style> [--dry|-n]");
132
- console.error("Styles: title_underscore, snake, kebab, camel, pascal, upper, lower");
143
+ console.error("Styles: title_underscore, pascal_underscore, snake, kebab, camel, pascal, upper, lower");
133
144
  process.exit(1);
134
145
  }
135
146
  renameRecursive(rootArg, styleArg, { dryRun }).then((performed) => {
package/bin/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env node
2
+
1
3
  // src/index.ts
2
4
  import { readFile } from "node:fs/promises";
3
5
  import { exec } from "node:child_process";
@@ -76,6 +78,142 @@ async function executeCommand(command, variables) {
76
78
  });
77
79
  });
78
80
  }
81
+ async function showHelp() {
82
+ async function getCommandDescription(filePath) {
83
+ try {
84
+ const content = await readFile(filePath, "utf-8");
85
+ const descMatch = content.match(/(?:\/\/|#)\s*@description\s+(.+)/);
86
+ if (descMatch) {
87
+ return descMatch[1].trim();
88
+ }
89
+ const dir = path.dirname(filePath);
90
+ const readmePath = path.join(dir, "README.md");
91
+ if (fs.existsSync(readmePath)) {
92
+ const readme = await readFile(readmePath, "utf-8");
93
+ const firstLine = readme.split(`
94
+ `).find((line) => line.trim() && !line.startsWith("#"));
95
+ if (firstLine) {
96
+ return firstLine.trim();
97
+ }
98
+ }
99
+ } catch (error) {}
100
+ return;
101
+ }
102
+ async function discoverCommands(commandsDir) {
103
+ const commands2 = [];
104
+ try {
105
+ const categories = await fs.promises.readdir(commandsDir, { withFileTypes: true });
106
+ for (const category of categories) {
107
+ if (!category.isDirectory())
108
+ continue;
109
+ const categoryPath = path.join(commandsDir, category.name);
110
+ const items = await fs.promises.readdir(categoryPath, { withFileTypes: true });
111
+ for (const item of items) {
112
+ if (item.isDirectory()) {
113
+ const nestedJsPath = path.join(categoryPath, item.name, `${item.name}.js`);
114
+ const nestedShPath = path.join(categoryPath, item.name, `${item.name}.sh`);
115
+ let commandPath;
116
+ let commandType;
117
+ if (fs.existsSync(nestedJsPath)) {
118
+ commandPath = nestedJsPath;
119
+ commandType = "typescript";
120
+ } else if (fs.existsSync(nestedShPath)) {
121
+ commandPath = nestedShPath;
122
+ commandType = "shell";
123
+ } else {
124
+ continue;
125
+ }
126
+ const description = await getCommandDescription(commandPath);
127
+ commands2.push({
128
+ category: category.name,
129
+ name: item.name,
130
+ type: commandType,
131
+ path: commandPath,
132
+ description
133
+ });
134
+ } else if (item.name.endsWith(".js")) {
135
+ const jsPath = path.join(categoryPath, item.name);
136
+ const commandName = path.basename(item.name, ".js");
137
+ if (commandName !== "test" && !commandName.endsWith(".test")) {
138
+ const description = await getCommandDescription(jsPath);
139
+ commands2.push({
140
+ category: category.name,
141
+ name: commandName,
142
+ type: "typescript",
143
+ path: jsPath,
144
+ description
145
+ });
146
+ }
147
+ } else if (item.name.endsWith(".sh")) {
148
+ const shPath = path.join(categoryPath, item.name);
149
+ const commandName = path.basename(item.name, ".sh");
150
+ const description = await getCommandDescription(shPath);
151
+ commands2.push({
152
+ category: category.name,
153
+ name: commandName,
154
+ type: "shell",
155
+ path: shPath,
156
+ description
157
+ });
158
+ }
159
+ }
160
+ }
161
+ } catch (error) {}
162
+ return commands2;
163
+ }
164
+ const version = await getVersion();
165
+ const binCommandsDir = path.join(__dirname2, "commands");
166
+ const srcCommandsDir = path.join(__dirname2, "..", "src", "commands");
167
+ const commands = [
168
+ ...await discoverCommands(binCommandsDir),
169
+ ...await discoverCommands(srcCommandsDir)
170
+ ];
171
+ const uniqueCommands = commands.filter((cmd, index, self) => index === self.findIndex((c) => c.category === cmd.category && c.name === cmd.name));
172
+ console.log(`
173
+ ╔════════════════════════════════════════════════════════════╗`);
174
+ console.log(`║ hlpr - Helper CLI Tool v${version.padEnd(16)}║`);
175
+ console.log(`╚════════════════════════════════════════════════════════════╝
176
+ `);
177
+ console.log("USAGE:");
178
+ console.log(` hlpr [options] <category> <command> [args...]
179
+ `);
180
+ console.log("OPTIONS:");
181
+ console.log(" -f Force execution (continue on errors)");
182
+ console.log(" -v, --version Show version information");
183
+ console.log(" help, -h, --help, /h, /help, /?");
184
+ console.log(` Show this help message
185
+ `);
186
+ console.log(`AVAILABLE COMMANDS:
187
+ `);
188
+ const grouped = uniqueCommands.reduce((acc, cmd) => {
189
+ if (!acc[cmd.category]) {
190
+ acc[cmd.category] = [];
191
+ }
192
+ acc[cmd.category].push(cmd);
193
+ return acc;
194
+ }, {});
195
+ const sortedCategories = Object.keys(grouped).sort();
196
+ for (const category of sortedCategories) {
197
+ console.log(` ${category}:`);
198
+ const categoryCommands = grouped[category].sort((a, b) => a.name.localeCompare(b.name));
199
+ for (const cmd of categoryCommands) {
200
+ const typeLabel = cmd.type === "typescript" ? "(TS)" : "(sh)";
201
+ const cmdDisplay = cmd.category === cmd.name ? `hlpr ${cmd.name}`.padEnd(35) : `hlpr ${cmd.category} ${cmd.name}`.padEnd(35);
202
+ if (cmd.description) {
203
+ console.log(` ${cmdDisplay} ${typeLabel.padEnd(6)} ${cmd.description}`);
204
+ } else {
205
+ console.log(` ${cmdDisplay} ${typeLabel}`);
206
+ }
207
+ }
208
+ console.log("");
209
+ }
210
+ console.log("EXAMPLES:");
211
+ console.log(" hlpr help");
212
+ console.log(" hlpr file rename --style kebab --dir ./src");
213
+ console.log(" hlpr ssh init-dir");
214
+ console.log(` hlpr -f git precommit
215
+ `);
216
+ }
79
217
  async function main() {
80
218
  if (commandArgs[0] === "--version" || commandArgs[0] === "-v") {
81
219
  const version = await getVersion();
@@ -83,17 +221,8 @@ async function main() {
83
221
  process.exit(0);
84
222
  }
85
223
  if (commandArgs.length === 0 || commandArgs[0] === "help" || commandArgs[0] === "--help" || commandArgs[0] === "-h" || commandArgs[0] === "/help" || commandArgs[0] === "/h" || commandArgs[0] === "/?") {
86
- const helpScriptPath = path.join(__dirname2, "..", "bin", "commands", "help", "help.js");
87
- if (fs.existsSync(helpScriptPath)) {
88
- const helpCommand = `node "${helpScriptPath}"`;
89
- await executeCommand(helpCommand, {});
90
- rl.close();
91
- process.exit(0);
92
- } else {
93
- console.error("Please provide a command. Example: hlpr ssh init-dir");
94
- console.error("Run 'hlpr help' for available commands.");
95
- process.exit(1);
96
- }
224
+ await showHelp();
225
+ process.exit(0);
97
226
  }
98
227
  try {
99
228
  const category = commandArgs[0];
@@ -133,11 +262,20 @@ async function main() {
133
262
  process.exit(1);
134
263
  }
135
264
  if (isTypeScriptCommand) {
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);
265
+ let tsArgs;
266
+ if (restArgs.length > 0 && restArgs[0] && scriptPath) {
267
+ const subcategory = restArgs[0];
268
+ const isNested = path.basename(scriptPath) === `${subcategory}.js` && path.basename(path.dirname(scriptPath)) === subcategory;
269
+ tsArgs = isNested ? process.argv.slice(4) : process.argv.slice(3);
270
+ } else {
271
+ tsArgs = process.argv.slice(3);
272
+ }
137
273
  const finalCommand = `node "${scriptPath}" ${tsArgs.join(" ")}`;
138
274
  console.log(`Executing command: ${finalCommand}`);
139
275
  const success2 = await executeCommand(finalCommand, {});
140
- if (!success2 && !forceFlag) {
276
+ const helpFlags = ["-h", "--help", "help", "/h", "/help", "/?"];
277
+ const isHelpInvocation = tsArgs.some((arg) => helpFlags.includes(arg));
278
+ if (!success2 && !forceFlag && !isHelpInvocation) {
141
279
  console.error("Command failed, stopping execution.");
142
280
  process.exit(1);
143
281
  }
@@ -179,9 +317,7 @@ async function main() {
179
317
  rl.close();
180
318
  }
181
319
  }
182
- if (import.meta.url.startsWith("file:") && process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"))) {
183
- main();
184
- }
185
- export {
186
- main
187
- };
320
+ main().catch((error) => {
321
+ console.error("Fatal error:", error);
322
+ process.exit(1);
323
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tertium/hlpr",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Windows and *Nix utility for typical programming activity",
5
5
  "author": "Vitalii Balabanov",
6
6
  "email": "tertiumnon@gmail.com",
@@ -11,6 +11,7 @@
11
11
  "build:commands": "node build-commands.js",
12
12
  "build": "bun run build:main && bun run build:commands",
13
13
  "test": "bun test",
14
+ "test:e2e": "node scripts/test-rename-e2e.cjs",
14
15
  "prepublishOnly": "npm run build",
15
16
  "release:minor": "node node_modules/@tertium/js/scripts/release.js minor",
16
17
  "release:patch": "node node_modules/@tertium/js/scripts/release.js patch",