Skip to content

Commit 3798c3d

Browse files
Merge pull request #9 from udithavithanage/feature/update-checker
Enhance CLI UX, Add Version Auto-Check, and Refactor Configuration & Editor Logic
2 parents 810d6c6 + 5122d98 commit 3798c3d

File tree

10 files changed

+366
-82
lines changed

10 files changed

+366
-82
lines changed

bun.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "thyra",
3-
"version": "1.0.6",
3+
"version": "1.1.0",
44
"description": "Simple project folder opener CLI",
55
"type": "module",
66
"bin": {
@@ -39,5 +39,8 @@
3939
"@types/node": "^24.10.1",
4040
"bun": "^1.3.3",
4141
"typescript": "^5.9.3"
42+
},
43+
"dependencies": {
44+
"picocolors": "^1.1.1"
4245
}
4346
}

src/cli.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,40 +9,34 @@ import { getConfigFilePath, ConfigStore } from "~/configStore";
99
(function run() {
1010
const [, , command, ...rest] = process.argv;
1111

12-
const configPath = getConfigFilePath();
13-
const store = new ConfigStore(configPath);
12+
const store = new ConfigStore(getConfigFilePath());
1413

1514
if (
1615
!command ||
1716
command === "help" ||
1817
command === "--help" ||
1918
command === "-h"
2019
) {
21-
runHelp();
20+
runHelp(0);
2221
return;
2322
}
24-
2523
if (command === "version" || command === "--version" || command === "-v") {
26-
runVersion();
24+
runVersion(store);
2725
return;
2826
}
2927

3028
switch (command) {
3129
case "config":
3230
runConfig(store, rest);
3331
break;
34-
3532
case "open":
3633
runOpen(store, rest);
3734
break;
38-
3935
case "list":
4036
runList(store);
4137
break;
42-
4338
default:
4439
console.error(`Unknown command: ${command}`);
4540
runHelp(1);
46-
break;
4741
}
4842
})();

src/color-logs.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import color from "picocolors";
2+
3+
// Strip ANSI escape sequences (for width calc)
4+
export function stripAnsi(s: string) {
5+
return String(s).replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
6+
}
7+
8+
// Visible length (ignores ANSI)
9+
export function vlen(s: string) {
10+
return stripAnsi(s).length;
11+
}
12+
13+
// Pad right by visible width
14+
export function padRight(s: string, width: number) {
15+
const diff = Math.max(0, width - vlen(s));
16+
return s + " ".repeat(diff);
17+
}
18+
19+
export function colorize(cmd: string) {
20+
return cmd.replace(/thyra\b|--\S+|<[^>]+>/g, (tok) => {
21+
if (tok === "thyra") return color.green(tok);
22+
if (tok.startsWith("--")) return color.yellow(tok);
23+
if (tok.startsWith("<")) return color.cyan(tok);
24+
return tok;
25+
});
26+
}
27+
28+
export interface CommandTableRow {
29+
Command: string;
30+
Description: string;
31+
}
32+
33+
interface PrintCommandTableOptions {
34+
header?: {
35+
Command: string;
36+
Description: string;
37+
};
38+
}
39+
40+
export function printCommandTable(
41+
rows: CommandTableRow[],
42+
options: PrintCommandTableOptions = {},
43+
) {
44+
const header = options.header ?? {
45+
Command: "Command",
46+
Description: "Description",
47+
};
48+
49+
const col1 = [header.Command, ...rows.map((r) => r.Command)];
50+
const col2 = [header.Description, ...rows.map((r) => r.Description)];
51+
52+
const w1 = Math.max(...col1.map(vlen));
53+
const w2 = Math.max(...col2.map(vlen));
54+
55+
const sep = " "; // spacing between columns
56+
const line = (t1: string, t2: string) =>
57+
padRight(t1, w1) + sep + padRight(t2, w2);
58+
59+
// header
60+
console.log(color.bold(line(header.Command, header.Description)));
61+
// separator
62+
console.log(padRight("-".repeat(w1), w1) + sep + "-".repeat(w2));
63+
64+
// rows
65+
for (const r of rows) {
66+
console.log(line(r.Command, r.Description));
67+
}
68+
}

src/commands/help.ts

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
1-
export function runHelp(exitCode?: number): void {
2-
console.log(`
3-
thyra - Quick shortcut manager for project folders
4-
`);
1+
import color from "picocolors";
52

6-
console.table([
3+
import { colorize, printCommandTable } from "~/color-logs";
4+
5+
export function runHelp(exitCode: number) {
6+
console.log(
7+
`\n${color.bold(color.cyan("thyra"))} ${color.dim(
8+
"- Quick shortcut manager for project folders",
9+
)}\n`,
10+
);
11+
12+
const rows = [
713
{
8-
Command: "thyra config <name> <folder_path>",
14+
Command: colorize("thyra config <name> <folder_path>"),
915
Description: "Save a folder path",
1016
},
11-
{ Command: "thyra open <name>", Description: "Open folder in your editor" },
12-
{ Command: "thyra list", Description: "Show all saved paths" },
13-
{ Command: "thyra --version", Description: "Show CLI version" },
14-
{ Command: "thyra --help", Description: "Show this help" },
15-
]);
17+
{
18+
Command: colorize("thyra open <name>"),
19+
Description: "Open folder in your editor",
20+
},
21+
{ Command: colorize("thyra list"), Description: "Show all saved paths" },
22+
{ Command: colorize("thyra --version"), Description: "Show CLI version" },
23+
{ Command: colorize("thyra --help"), Description: "Show this help" },
24+
];
25+
26+
printCommandTable(rows);
1627

17-
console.log(`
18-
Examples:
19-
thyra config my-app ~/projects/my-app
20-
thyra open my-app
21-
thyra --version
28+
console.log(
29+
`\n${color.bold(color.underline("Examples:"))}
30+
${colorize("thyra config <name> <folder_path>")} ${color.dim("# Save a path")}
31+
${colorize("thyra open <name>")} ${color.dim(
32+
"# Open in editor",
33+
)}
34+
${colorize("thyra --version")}
2235
23-
Environment:
24-
THYRA_EDITOR Editor command (default: "code")
25-
`);
36+
${color.bold(color.underline("Environment:"))}
37+
${color.cyan("THYRA_EDITOR")} ${color.dim('Editor command (default: "code")')}
38+
`,
39+
);
2640

27-
if (typeof exitCode === "number") {
28-
process.exit(exitCode);
29-
}
41+
if (typeof exitCode === "number") process.exit(exitCode);
3042
}

src/commands/list.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
1+
import color from "picocolors";
2+
3+
import { colorize, printCommandTable } from "~/color-logs";
4+
15
import type { ConfigStore } from "~/configStore";
26

3-
export function runList(store: ConfigStore): void {
7+
export function runList(store: ConfigStore) {
48
const all = store.all();
59
const keys = Object.keys(all);
610

711
if (keys.length === 0) {
8-
console.log("No folders saved yet.");
9-
console.log("Use: thyra config <name> <folder_path>");
12+
console.log(color.dim("No folders saved yet."));
13+
console.log("Use: " + colorize("thyra config <name> <folder_path>"));
1014
return;
1115
}
1216

1317
const rows = keys.map((key) => ({
14-
Name: key,
15-
Path: all[key],
18+
Command: color.cyan(key),
19+
Description: color.dim(all[key]),
1620
}));
1721

18-
console.log("\nSaved folders:\n");
19-
console.table(rows);
22+
console.log("\n" + color.bold("Saved folders:\n"));
23+
24+
printCommandTable(rows, {
25+
header: {
26+
Command: "Name",
27+
Description: "Path",
28+
},
29+
});
2030
}

src/commands/open.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
import fs from "node:fs";
2-
import { openInEditor } from "~/editor.js";
2+
import { exec } from "node:child_process";
3+
4+
function openInEditor(folderPath: string) {
5+
const editorCmd = process.env.THYRA_EDITOR || "code";
6+
let safePath = folderPath;
7+
if (editorCmd.trim() !== "explorer") {
8+
safePath = folderPath.replace(/(["\\$`])/g, "\\$1");
9+
}
10+
const command = `${editorCmd} "${safePath}"`;
11+
12+
console.log(`Opening "${folderPath}" in "${editorCmd}"...`);
13+
14+
const child = exec(command, (error, stdout, stderr) => {
15+
if (stdout) {
16+
process.stdout.write(stdout);
17+
}
18+
if (stderr) {
19+
process.stderr.write(stderr);
20+
}
21+
22+
if (error && editorCmd !== "explorer") {
23+
console.error(
24+
`Failed to start editor "${editorCmd}". Is it installed and on your PATH?`,
25+
);
26+
console.error(error.message);
27+
process.exit(1);
28+
}
29+
});
30+
31+
if (child.stdout) child.stdout.pipe(process.stdout);
32+
if (child.stderr) child.stderr.pipe(process.stderr);
33+
}
334

435
import type { ConfigStore } from "~/configStore";
536

0 commit comments

Comments
 (0)