@jannael/glinter 1.1.0 → 1.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 +93 -8
- package/dist/index.js +749 -111
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -1,23 +1,106 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<br>
|
|
3
|
+
<br>
|
|
4
|
+
<a href="https://glinter.jannael.com" target="_blank" rel="noopener noreferrer">
|
|
5
|
+
<picture>
|
|
6
|
+
<img alt="Glinter" src="https://github.com/Jannael/glinter/raw/main/apps/web/public/og.png">
|
|
7
|
+
</picture>
|
|
8
|
+
</a>
|
|
9
|
+
<br>
|
|
10
|
+
<br>
|
|
11
|
+
<br>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
Glinter is a high-performance, transparent Git wrapper built with **Bun**.
|
|
4
15
|
|
|
5
16
|
## Preview
|
|
6
17
|
|
|
7
|
-
<video src="https://github.com/user-attachments/assets/
|
|
18
|
+
<video src="https://github.com/user-attachments/assets/63b401a0-e1e1-453c-9e38-c36cd14e200f" controls="false" autoplay="true" loop="true" muted="true" style="max-width: 100%;">
|
|
8
19
|
Your browser does not support the video tag.
|
|
9
20
|
</video>
|
|
10
21
|
|
|
11
|
-
|
|
12
22
|
## Features
|
|
13
23
|
|
|
14
|
-
- **
|
|
24
|
+
- **Abbreviation**: You can use `g` instead of `git`.
|
|
25
|
+
|
|
26
|
+
- **Safe by Default**: Automatically filters and prevents accidental staging of sensitive files: `.env` and `node_modules`.
|
|
15
27
|
|
|
16
28
|
- **Transparent Wrapper**: For every other command (like `commit`, `push`, `log`, or `status`), Glinter acts as a direct tunnel to Git. It preserves all original colors, formatting, and interactive features of the native Git CLI.
|
|
17
29
|
|
|
18
|
-
- **Safe by Default**: Automatically filters and prevents accidental staging of sensitive files: `.env` and `node_modules`.
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
### Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `g add` | Opens an interactive file selector to stage changes, filtering out sensitive files like `.env` and `node_modules` |
|
|
36
|
+
| `g commit` | Opens an interactive prompt to select a commit type and write a commit message |
|
|
37
|
+
| `g switch` | Opens an interactive prompt to switch branches |
|
|
38
|
+
| `g setup` | sets up alias for git and glinter |
|
|
39
|
+
| `g alias` | shows all the aliases |
|
|
40
|
+
|
|
41
|
+
### Aliases
|
|
42
|
+
|
|
43
|
+
| Alias | Command | Description |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| gs | git status -sb | Status with short format and branch info |
|
|
46
|
+
| gl | git log --oneline --decorate --graph --all -n 20 | Recent commits graph |
|
|
47
|
+
| gll | git log --stat | Log with file change statistics |
|
|
48
|
+
| gd | git diff --word-diff=color | Word-level diff with colors |
|
|
49
|
+
| gds | git diff --staged --word-diff=color | Staged diff with colors |
|
|
50
|
+
| ga | glinter add | Interactive staging |
|
|
51
|
+
| gaa | git add -A | Add all changes |
|
|
52
|
+
| gc | glinter commit | Interactive commit |
|
|
53
|
+
| gcm | git commit -m | Direct commit message |
|
|
54
|
+
| gca | git commit --amend | Amend last commit |
|
|
55
|
+
| gcan | git commit --amend --no-edit | Amend without editing |
|
|
56
|
+
| gb | git branch | List branches |
|
|
57
|
+
| gba | git branch -a | List all branches |
|
|
58
|
+
| gco | glinter switch | Interactive branch switch |
|
|
59
|
+
| gcb | git checkout -b | Create and switch branch |
|
|
60
|
+
| gpl | git pull | Pull from remote |
|
|
61
|
+
| gplr | git pull --rebase | Pull with rebase |
|
|
62
|
+
| gp | git push | Push to remote |
|
|
63
|
+
| ggpush | git push origin HEAD | Push current branch |
|
|
64
|
+
| gpf | git push --force-with-lease | Force push with lease |
|
|
65
|
+
| gst | git stash | Stash changes |
|
|
66
|
+
| gstp | git stash pop | Pop stash |
|
|
67
|
+
| gstl | git stash list | List stashes |
|
|
68
|
+
| gcl | git clean -fd | Clean untracked files |
|
|
69
|
+
| grh | git reset --hard | Hard reset |
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
1. **Install Glinter**:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install -g @jannael/glinter
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. **Set up aliases** (optional, recommended):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
g setup
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. **Start using Glinter**:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
g add # Interactive file staging
|
|
89
|
+
g commit # Interactive commit
|
|
90
|
+
g status # Standard git status
|
|
91
|
+
g push # Standard git push
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
### Screenshots
|
|
98
|
+
|
|
99
|
+
| `g add` | `g commit` |
|
|
100
|
+
|---|---|
|
|
101
|
+
|  |  |
|
|
102
|
+
|
|
103
|
+
|
|
21
104
|
|
|
22
105
|
## How it works
|
|
23
106
|
|
|
@@ -52,7 +135,9 @@ npm install -g @jannael/glinter
|
|
|
52
135
|
now you can simply run:
|
|
53
136
|
```bash
|
|
54
137
|
g add # Opens the interactive selector
|
|
138
|
+
g commit # Opens commit type + message prompt
|
|
55
139
|
g add <file> # Runs standard git add <file>
|
|
140
|
+
g commit -m "" # Runs standard git commit -m ""
|
|
56
141
|
g status # Runs standard git status
|
|
57
142
|
g push # Runs standard git push
|
|
58
143
|
```
|
package/dist/index.js
CHANGED
|
@@ -90,22 +90,64 @@ var require_src = __commonJS((exports, module) => {
|
|
|
90
90
|
module.exports = { cursor, scroll, erase, beep };
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
// apps/cli/error/error-constructor.ts
|
|
94
|
+
function CreateError(name) {
|
|
95
|
+
const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
|
|
96
|
+
return class extends Error {
|
|
97
|
+
description;
|
|
98
|
+
constructor(message, description) {
|
|
99
|
+
super(capitalize(message));
|
|
100
|
+
this.name = name;
|
|
101
|
+
this.description = description !== undefined ? capitalize(description) : undefined;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// apps/cli/error/error-instance.ts
|
|
107
|
+
var NotFound = CreateError("NotFound");
|
|
108
|
+
var Forbidden = CreateError("Forbidden");
|
|
109
|
+
var Conflict = CreateError("Conflict");
|
|
110
|
+
var ServerError = CreateError("ServerError");
|
|
111
|
+
var BadRequest = CreateError("BadRequest");
|
|
112
|
+
|
|
113
|
+
// apps/cli/utils/colors.ts
|
|
101
114
|
var RESET = "\x1B[0m";
|
|
115
|
+
var GREEN = ({ text }) => `\x1B[32m${text}${RESET}`;
|
|
116
|
+
var YELLOW = ({ text }) => `\x1B[33m${text}${RESET}`;
|
|
117
|
+
var RED = ({ text }) => `\x1B[31m${text}${RESET}`;
|
|
118
|
+
var MAGENTA = ({ text }) => `\x1B[35m${text}${RESET}`;
|
|
119
|
+
var BLUE = ({ text }) => `\x1B[34m${text}${RESET}`;
|
|
120
|
+
var BLACK = ({ text }) => `\x1B[30m${text}${RESET}`;
|
|
121
|
+
var BG_YELLOW = ({ text }) => `\x1B[43m${text}${RESET}`;
|
|
122
|
+
|
|
123
|
+
// apps/cli/utils/icons-terminal.ts
|
|
124
|
+
var X = ({ text }) => RED({ text: `\u2716 ${text}` });
|
|
125
|
+
var CHECK = ({ text }) => `${GREEN({ text: "\u2714" })} ${text}`;
|
|
126
|
+
var WARNING = ({ text }) => BG_YELLOW({ text: BLACK({ text: ` \u26A0 ${text}` }) });
|
|
102
127
|
|
|
103
|
-
//
|
|
104
|
-
|
|
128
|
+
// apps/cli/error/error-handler.ts
|
|
129
|
+
function errorHandler(error) {
|
|
130
|
+
if (error instanceof ServerError) {
|
|
131
|
+
console.error(X({ text: error.message }));
|
|
132
|
+
if (error.description) {
|
|
133
|
+
console.error(` ${error.description}`);
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (error instanceof NotFound) {
|
|
138
|
+
console.error(WARNING({ text: error.message }));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
console.error(X({ text: "An unexpected error occurred" }));
|
|
142
|
+
if (error instanceof Error) {
|
|
143
|
+
console.error(` ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
105
146
|
|
|
106
147
|
// node_modules/@clack/core/dist/index.mjs
|
|
107
148
|
import { styleText as y } from "util";
|
|
108
149
|
import { stdout as S, stdin as $ } from "process";
|
|
150
|
+
import * as _ from "readline";
|
|
109
151
|
import P from "readline";
|
|
110
152
|
|
|
111
153
|
// node_modules/fast-string-truncated-width/dist/utils.js
|
|
@@ -494,6 +536,7 @@ function wrapAnsi(string, columns, options) {
|
|
|
494
536
|
|
|
495
537
|
// node_modules/@clack/core/dist/index.mjs
|
|
496
538
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
539
|
+
import { ReadStream as D } from "tty";
|
|
497
540
|
function d(r, t, e) {
|
|
498
541
|
if (!e.some((o) => !o.disabled))
|
|
499
542
|
return r;
|
|
@@ -530,6 +573,28 @@ function w(r, t) {
|
|
|
530
573
|
const e = r;
|
|
531
574
|
e.isTTY && e.setRawMode(t);
|
|
532
575
|
}
|
|
576
|
+
function z({ input: r = $, output: t = S, overwrite: e = true, hideCursor: s = true } = {}) {
|
|
577
|
+
const i = _.createInterface({ input: r, output: t, prompt: "", tabSize: 1 });
|
|
578
|
+
_.emitKeypressEvents(r, i), r instanceof D && r.isTTY && r.setRawMode(true);
|
|
579
|
+
const n = (o, { name: a, sequence: h }) => {
|
|
580
|
+
const l = String(o);
|
|
581
|
+
if (V([l, a, h], "cancel")) {
|
|
582
|
+
s && t.write(import_sisteransi.cursor.show), process.exit(0);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (!e)
|
|
586
|
+
return;
|
|
587
|
+
const f = a === "return" ? 0 : -1, v = a === "return" ? -1 : 0;
|
|
588
|
+
_.moveCursor(t, f, v, () => {
|
|
589
|
+
_.clearLine(t, 1, () => {
|
|
590
|
+
r.once("keypress", n);
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
};
|
|
594
|
+
return s && t.write(import_sisteransi.cursor.hide), r.once("keypress", n), () => {
|
|
595
|
+
r.off("keypress", n), s && t.write(import_sisteransi.cursor.show), r instanceof D && r.isTTY && !Y && r.setRawMode(false), i.terminal = false, i.close();
|
|
596
|
+
};
|
|
597
|
+
}
|
|
533
598
|
var O = (r) => ("columns" in r) && typeof r.columns == "number" ? r.columns : 80;
|
|
534
599
|
var A = (r) => ("rows" in r) && typeof r.rows == "number" ? r.rows : 20;
|
|
535
600
|
function R(r, t, e, s = e) {
|
|
@@ -744,9 +809,27 @@ var H = class extends p {
|
|
|
744
809
|
}
|
|
745
810
|
}
|
|
746
811
|
};
|
|
747
|
-
|
|
812
|
+
|
|
813
|
+
class Q extends p {
|
|
814
|
+
get cursor() {
|
|
815
|
+
return this.value ? 0 : 1;
|
|
816
|
+
}
|
|
817
|
+
get _value() {
|
|
818
|
+
return this.cursor === 0;
|
|
819
|
+
}
|
|
820
|
+
constructor(t) {
|
|
821
|
+
super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
|
|
822
|
+
this.value = this._value;
|
|
823
|
+
}), this.on("confirm", (e) => {
|
|
824
|
+
this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = e, this.state = "submit", this.close();
|
|
825
|
+
}), this.on("cursor", () => {
|
|
826
|
+
this.value = !this.value;
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
var X2 = { Y: { type: "year", len: 4 }, M: { type: "month", len: 2 }, D: { type: "day", len: 2 } };
|
|
748
831
|
function L(r) {
|
|
749
|
-
return [...r].map((t) =>
|
|
832
|
+
return [...r].map((t) => X2[t]);
|
|
750
833
|
}
|
|
751
834
|
function Z(r) {
|
|
752
835
|
const t = new Intl.DateTimeFormat(r, { year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(new Date(2000, 0, 15)), e = [];
|
|
@@ -1048,6 +1131,27 @@ class nt extends p {
|
|
|
1048
1131
|
});
|
|
1049
1132
|
}
|
|
1050
1133
|
}
|
|
1134
|
+
class at extends p {
|
|
1135
|
+
get userInputWithCursor() {
|
|
1136
|
+
if (this.state === "submit")
|
|
1137
|
+
return this.userInput;
|
|
1138
|
+
const t = this.userInput;
|
|
1139
|
+
if (this.cursor >= t.length)
|
|
1140
|
+
return `${this.userInput}\u2588`;
|
|
1141
|
+
const e = t.slice(0, this.cursor), [s, ...i] = t.slice(this.cursor);
|
|
1142
|
+
return `${e}${y("inverse", s)}${i.join("")}`;
|
|
1143
|
+
}
|
|
1144
|
+
get cursor() {
|
|
1145
|
+
return this._cursor;
|
|
1146
|
+
}
|
|
1147
|
+
constructor(t) {
|
|
1148
|
+
super({ ...t, initialUserInput: t.initialUserInput ?? t.initialValue }), this.on("userInput", (e) => {
|
|
1149
|
+
this._setValue(e);
|
|
1150
|
+
}), this.on("finalize", () => {
|
|
1151
|
+
this.value || (this.value = t.defaultValue), this.value === undefined && (this.value = "");
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1051
1155
|
|
|
1052
1156
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
1053
1157
|
import { styleText as t, stripVTControlCharacters as ne } from "util";
|
|
@@ -1057,6 +1161,7 @@ function Ze() {
|
|
|
1057
1161
|
return P2.platform !== "win32" ? P2.env.TERM !== "linux" : !!P2.env.CI || !!P2.env.WT_SESSION || !!P2.env.TERMINUS_SUBLIME || P2.env.ConEmuTask === "{cmd::Cmder}" || P2.env.TERM_PROGRAM === "Terminus-Sublime" || P2.env.TERM_PROGRAM === "vscode" || P2.env.TERM === "xterm-256color" || P2.env.TERM === "alacritty" || P2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
1058
1162
|
}
|
|
1059
1163
|
var ee = Ze();
|
|
1164
|
+
var ae = () => process.env.CI === "true";
|
|
1060
1165
|
var w2 = (e, i) => ee ? e : i;
|
|
1061
1166
|
var _e = w2("\u25C6", "*");
|
|
1062
1167
|
var oe = w2("\u25A0", "x");
|
|
@@ -1134,7 +1239,7 @@ var Y2 = ({ cursor: e, options: i, style: s, output: r = process.stdout, maxItem
|
|
|
1134
1239
|
}
|
|
1135
1240
|
if (f > $2) {
|
|
1136
1241
|
let b = 0, x = 0, G2 = f;
|
|
1137
|
-
const M2 = e - v, R2 = (j2,
|
|
1242
|
+
const M2 = e - v, R2 = (j2, D2) => et2(h, G2, j2, D2, $2);
|
|
1138
1243
|
m ? ({ lineCount: G2, removals: b } = R2(0, M2), G2 > $2 && ({ lineCount: G2, removals: x } = R2(M2 + 1, h.length))) : ({ lineCount: G2, removals: x } = R2(M2 + 1, h.length), G2 > $2 && ({ lineCount: G2, removals: b } = R2(0, M2))), b > 0 && (m = true, h.splice(0, b)), x > 0 && (g = true, h.splice(h.length - x, x));
|
|
1139
1244
|
}
|
|
1140
1245
|
const C2 = [];
|
|
@@ -1144,10 +1249,49 @@ var Y2 = ({ cursor: e, options: i, style: s, output: r = process.stdout, maxItem
|
|
|
1144
1249
|
C2.push(x);
|
|
1145
1250
|
return g && C2.push(l), C2;
|
|
1146
1251
|
};
|
|
1252
|
+
var ot2 = (e) => {
|
|
1253
|
+
const i = e.active ?? "Yes", s = e.inactive ?? "No";
|
|
1254
|
+
return new Q({ active: i, inactive: s, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue ?? true, render() {
|
|
1255
|
+
const r = e.withGuide ?? u.withGuide, u2 = `${V2(this.state)} `, n = r ? `${t("gray", d2)} ` : "", o = R(e.output, e.message, n, u2), c2 = `${r ? `${t("gray", d2)}
|
|
1256
|
+
` : ""}${o}
|
|
1257
|
+
`, a = this.value ? i : s;
|
|
1258
|
+
switch (this.state) {
|
|
1259
|
+
case "submit": {
|
|
1260
|
+
const l = r ? `${t("gray", d2)} ` : "";
|
|
1261
|
+
return `${c2}${l}${t("dim", a)}`;
|
|
1262
|
+
}
|
|
1263
|
+
case "cancel": {
|
|
1264
|
+
const l = r ? `${t("gray", d2)} ` : "";
|
|
1265
|
+
return `${c2}${l}${t(["strikethrough", "dim"], a)}${r ? `
|
|
1266
|
+
${t("gray", d2)}` : ""}`;
|
|
1267
|
+
}
|
|
1268
|
+
default: {
|
|
1269
|
+
const l = r ? `${t("cyan", d2)} ` : "", $2 = r ? t("cyan", E2) : "";
|
|
1270
|
+
return `${c2}${l}${this.value ? `${t("green", z2)} ${i}` : `${t("dim", H2)} ${t("dim", i)}`}${e.vertical ? r ? `
|
|
1271
|
+
${t("cyan", d2)} ` : `
|
|
1272
|
+
` : ` ${t("dim", "/")} `}${this.value ? `${t("dim", H2)} ${t("dim", s)}` : `${t("green", z2)} ${s}`}
|
|
1273
|
+
${$2}
|
|
1274
|
+
`;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
} }).prompt();
|
|
1278
|
+
};
|
|
1147
1279
|
var pt = (e = "", i) => {
|
|
1148
1280
|
const s = i?.output ?? process.stdout, r = i?.withGuide ?? u.withGuide ? `${t("gray", E2)} ` : "";
|
|
1149
1281
|
s.write(`${r}${t("red", e)}
|
|
1150
1282
|
|
|
1283
|
+
`);
|
|
1284
|
+
};
|
|
1285
|
+
var mt = (e = "", i) => {
|
|
1286
|
+
const s = i?.output ?? process.stdout, r = i?.withGuide ?? u.withGuide ? `${t("gray", le)} ` : "";
|
|
1287
|
+
s.write(`${r}${e}
|
|
1288
|
+
`);
|
|
1289
|
+
};
|
|
1290
|
+
var gt = (e = "", i) => {
|
|
1291
|
+
const s = i?.output ?? process.stdout, r = i?.withGuide ?? u.withGuide ? `${t("gray", d2)}
|
|
1292
|
+
${t("gray", E2)} ` : "";
|
|
1293
|
+
s.write(`${r}${e}
|
|
1294
|
+
|
|
1151
1295
|
`);
|
|
1152
1296
|
};
|
|
1153
1297
|
var Q2 = (e, i) => e.split(`
|
|
@@ -1206,6 +1350,64 @@ ${r ? t("cyan", E2) : ""}
|
|
|
1206
1350
|
}
|
|
1207
1351
|
} }).prompt();
|
|
1208
1352
|
};
|
|
1353
|
+
var Ct = (e) => t("magenta", e);
|
|
1354
|
+
var fe = ({ indicator: e = "dots", onCancel: i, output: s = process.stdout, cancelMessage: r, errorMessage: u2, frames: n = ee ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: o = ee ? 80 : 120, signal: c2, ...a } = {}) => {
|
|
1355
|
+
const l = ae();
|
|
1356
|
+
let $2, y2, p2 = false, m = false, g = "", S2, h = performance.now();
|
|
1357
|
+
const f = O(s), v = a?.styleFrame ?? Ct, T2 = (_2) => {
|
|
1358
|
+
const A2 = _2 > 1 ? u2 ?? u.messages.error : r ?? u.messages.cancel;
|
|
1359
|
+
m = _2 === 1, p2 && (W2(A2, _2), m && typeof i == "function" && i());
|
|
1360
|
+
}, C2 = () => T2(2), b = () => T2(1), x = () => {
|
|
1361
|
+
process.on("uncaughtExceptionMonitor", C2), process.on("unhandledRejection", C2), process.on("SIGINT", b), process.on("SIGTERM", b), process.on("exit", T2), c2 && c2.addEventListener("abort", b);
|
|
1362
|
+
}, G2 = () => {
|
|
1363
|
+
process.removeListener("uncaughtExceptionMonitor", C2), process.removeListener("unhandledRejection", C2), process.removeListener("SIGINT", b), process.removeListener("SIGTERM", b), process.removeListener("exit", T2), c2 && c2.removeEventListener("abort", b);
|
|
1364
|
+
}, M2 = () => {
|
|
1365
|
+
if (S2 === undefined)
|
|
1366
|
+
return;
|
|
1367
|
+
l && s.write(`
|
|
1368
|
+
`);
|
|
1369
|
+
const _2 = wrapAnsi(S2, f, { hard: true, trim: false }).split(`
|
|
1370
|
+
`);
|
|
1371
|
+
_2.length > 1 && s.write(import_sisteransi2.cursor.up(_2.length - 1)), s.write(import_sisteransi2.cursor.to(0)), s.write(import_sisteransi2.erase.down());
|
|
1372
|
+
}, R2 = (_2) => _2.replace(/\.+$/, ""), j2 = (_2) => {
|
|
1373
|
+
const A2 = (performance.now() - _2) / 1000, k2 = Math.floor(A2 / 60), L2 = Math.floor(A2 % 60);
|
|
1374
|
+
return k2 > 0 ? `[${k2}m ${L2}s]` : `[${L2}s]`;
|
|
1375
|
+
}, D2 = a.withGuide ?? u.withGuide, ie = (_2 = "") => {
|
|
1376
|
+
p2 = true, $2 = z({ output: s }), g = R2(_2), h = performance.now(), D2 && s.write(`${t("gray", d2)}
|
|
1377
|
+
`);
|
|
1378
|
+
let A2 = 0, k2 = 0;
|
|
1379
|
+
x(), y2 = setInterval(() => {
|
|
1380
|
+
if (l && g === S2)
|
|
1381
|
+
return;
|
|
1382
|
+
M2(), S2 = g;
|
|
1383
|
+
const L2 = v(n[A2]);
|
|
1384
|
+
let Z2;
|
|
1385
|
+
if (l)
|
|
1386
|
+
Z2 = `${L2} ${g}...`;
|
|
1387
|
+
else if (e === "timer")
|
|
1388
|
+
Z2 = `${L2} ${g} ${j2(h)}`;
|
|
1389
|
+
else {
|
|
1390
|
+
const Be = ".".repeat(Math.floor(k2)).slice(0, 3);
|
|
1391
|
+
Z2 = `${L2} ${g}${Be}`;
|
|
1392
|
+
}
|
|
1393
|
+
const Ne = wrapAnsi(Z2, f, { hard: true, trim: false });
|
|
1394
|
+
s.write(Ne), A2 = A2 + 1 < n.length ? A2 + 1 : 0, k2 = k2 < 4 ? k2 + 0.125 : 0;
|
|
1395
|
+
}, o);
|
|
1396
|
+
}, W2 = (_2 = "", A2 = 0, k2 = false) => {
|
|
1397
|
+
if (!p2)
|
|
1398
|
+
return;
|
|
1399
|
+
p2 = false, clearInterval(y2), M2();
|
|
1400
|
+
const L2 = A2 === 0 ? t("green", F2) : A2 === 1 ? t("red", oe) : t("red", ue);
|
|
1401
|
+
g = _2 ?? g, k2 || (e === "timer" ? s.write(`${L2} ${g} ${j2(h)}
|
|
1402
|
+
`) : s.write(`${L2} ${g}
|
|
1403
|
+
`)), G2(), $2();
|
|
1404
|
+
};
|
|
1405
|
+
return { start: ie, stop: (_2 = "") => W2(_2, 0), message: (_2 = "") => {
|
|
1406
|
+
g = R2(_2 ?? g);
|
|
1407
|
+
}, cancel: (_2 = "") => W2(_2, 1), error: (_2 = "") => W2(_2, 2), clear: () => W2("", 0, true), get isCancelled() {
|
|
1408
|
+
return m;
|
|
1409
|
+
} };
|
|
1410
|
+
};
|
|
1209
1411
|
var Ve = { light: w2("\u2500", "-"), heavy: w2("\u2501", "="), block: w2("\u2588", "#") };
|
|
1210
1412
|
var re = (e, i) => e.includes(`
|
|
1211
1413
|
`) ? e.split(`
|
|
@@ -1253,8 +1455,37 @@ ${a}
|
|
|
1253
1455
|
} }).prompt();
|
|
1254
1456
|
};
|
|
1255
1457
|
var je = `${t("gray", d2)} `;
|
|
1458
|
+
var Ot = (e) => new at({ validate: e.validate, placeholder: e.placeholder, defaultValue: e.defaultValue, initialValue: e.initialValue, output: e.output, signal: e.signal, input: e.input, render() {
|
|
1459
|
+
const i = e?.withGuide ?? u.withGuide, s = `${`${i ? `${t("gray", d2)}
|
|
1460
|
+
` : ""}${V2(this.state)} `}${e.message}
|
|
1461
|
+
`, r = e.placeholder ? t("inverse", e.placeholder[0]) + t("dim", e.placeholder.slice(1)) : t(["inverse", "hidden"], "_"), u2 = this.userInput ? this.userInputWithCursor : r, n = this.value ?? "";
|
|
1462
|
+
switch (this.state) {
|
|
1463
|
+
case "error": {
|
|
1464
|
+
const o = this.error ? ` ${t("yellow", this.error)}` : "", c2 = i ? `${t("yellow", d2)} ` : "", a = i ? t("yellow", E2) : "";
|
|
1465
|
+
return `${s.trim()}
|
|
1466
|
+
${c2}${u2}
|
|
1467
|
+
${a}${o}
|
|
1468
|
+
`;
|
|
1469
|
+
}
|
|
1470
|
+
case "submit": {
|
|
1471
|
+
const o = n ? ` ${t("dim", n)}` : "", c2 = i ? t("gray", d2) : "";
|
|
1472
|
+
return `${s}${c2}${o}`;
|
|
1473
|
+
}
|
|
1474
|
+
case "cancel": {
|
|
1475
|
+
const o = n ? ` ${t(["strikethrough", "dim"], n)}` : "", c2 = i ? t("gray", d2) : "";
|
|
1476
|
+
return `${s}${c2}${o}${n.trim() ? `
|
|
1477
|
+
${c2}` : ""}`;
|
|
1478
|
+
}
|
|
1479
|
+
default: {
|
|
1480
|
+
const o = i ? `${t("cyan", d2)} ` : "", c2 = i ? t("cyan", E2) : "";
|
|
1481
|
+
return `${s}${o}${u2}
|
|
1482
|
+
${c2}
|
|
1483
|
+
`;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
} }).prompt();
|
|
1256
1487
|
|
|
1257
|
-
//
|
|
1488
|
+
// apps/cli/utils/multiselect.ts
|
|
1258
1489
|
async function MultiSelect({
|
|
1259
1490
|
message,
|
|
1260
1491
|
options
|
|
@@ -1271,7 +1502,7 @@ async function MultiSelect({
|
|
|
1271
1502
|
return selected;
|
|
1272
1503
|
}
|
|
1273
1504
|
|
|
1274
|
-
//
|
|
1505
|
+
// apps/cli/modules/add/app/add-command.ts
|
|
1275
1506
|
class AddCommand {
|
|
1276
1507
|
getChangesUseCase;
|
|
1277
1508
|
stageChangesUseCase;
|
|
@@ -1280,37 +1511,46 @@ class AddCommand {
|
|
|
1280
1511
|
this.stageChangesUseCase = stageChangesUseCase;
|
|
1281
1512
|
}
|
|
1282
1513
|
async execute() {
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1514
|
+
try {
|
|
1515
|
+
const { changes, warnings } = await this.getChangesUseCase.execute();
|
|
1516
|
+
if (changes.length === 0) {
|
|
1517
|
+
console.log(`${CHECK({ text: "All changes are either staged or sensitive (like .env)." })}`);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
const options = [
|
|
1521
|
+
...changes.map((c2) => ({ value: c2.value, label: c2.label }))
|
|
1522
|
+
];
|
|
1523
|
+
const selectedChanges = await MultiSelect({
|
|
1524
|
+
message: `Select the changes you want to commit.
|
|
1525
|
+
` + BLUE({ text: "[space] to select" }) + `
|
|
1526
|
+
` + GREEN({ text: "[enter] to confirm" }) + `
|
|
1527
|
+
` + MAGENTA({ text: "[a] to select all" }) + `
|
|
1528
|
+
` + RED({ text: "[esc] to cancel" }) + `
|
|
1529
|
+
`,
|
|
1530
|
+
options
|
|
1531
|
+
});
|
|
1532
|
+
let selected = selectedChanges.map((file) => file.trim());
|
|
1533
|
+
if (selected.includes("all")) {
|
|
1534
|
+
selected = changes.map((c2) => c2.value);
|
|
1535
|
+
}
|
|
1536
|
+
if (selected.length > 0) {
|
|
1537
|
+
await this.stageChangesUseCase.execute(selected);
|
|
1538
|
+
if (warnings.size > 0) {
|
|
1539
|
+
console.log(`
|
|
1540
|
+
${WARNING({ text: " WARNING " })}`);
|
|
1541
|
+
for (const warning of warnings) {
|
|
1542
|
+
console.log(warning);
|
|
1543
|
+
}
|
|
1544
|
+
console.log("");
|
|
1306
1545
|
}
|
|
1307
|
-
console.log("");
|
|
1308
1546
|
}
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
errorHandler(error);
|
|
1309
1549
|
}
|
|
1310
1550
|
}
|
|
1311
1551
|
}
|
|
1312
1552
|
|
|
1313
|
-
//
|
|
1553
|
+
// apps/cli/modules/add/domain/change.ts
|
|
1314
1554
|
class Change {
|
|
1315
1555
|
props;
|
|
1316
1556
|
constructor(props) {
|
|
@@ -1329,16 +1569,16 @@ class Change {
|
|
|
1329
1569
|
const { status, displayPath } = this.props;
|
|
1330
1570
|
let label = `${status}: ${displayPath}`;
|
|
1331
1571
|
if (status.includes("M")) {
|
|
1332
|
-
label =
|
|
1572
|
+
label = YELLOW({ text: `modified: ${displayPath}` });
|
|
1333
1573
|
}
|
|
1334
1574
|
if (status.includes("A") || status.includes("?")) {
|
|
1335
|
-
label =
|
|
1575
|
+
label = GREEN({ text: `new file: ${displayPath}` });
|
|
1336
1576
|
}
|
|
1337
1577
|
if (status.includes("D")) {
|
|
1338
|
-
label =
|
|
1578
|
+
label = RED({ text: `deleted: ${displayPath}` });
|
|
1339
1579
|
}
|
|
1340
1580
|
if (status.includes("R")) {
|
|
1341
|
-
label =
|
|
1581
|
+
label = MAGENTA({ text: `renamed: ${displayPath}` });
|
|
1342
1582
|
}
|
|
1343
1583
|
return label;
|
|
1344
1584
|
}
|
|
@@ -1347,10 +1587,10 @@ class Change {
|
|
|
1347
1587
|
}
|
|
1348
1588
|
getWarning() {
|
|
1349
1589
|
if (this.value.includes(".env")) {
|
|
1350
|
-
return
|
|
1590
|
+
return YELLOW({ text: " .env file hidden" }) + " (Add to .gitignore to avoid leaks)";
|
|
1351
1591
|
}
|
|
1352
1592
|
if (this.value.includes("node_modules")) {
|
|
1353
|
-
return
|
|
1593
|
+
return YELLOW({ text: " node_modules hidden" }) + " (Add to .gitignore to save space)";
|
|
1354
1594
|
}
|
|
1355
1595
|
return null;
|
|
1356
1596
|
}
|
|
@@ -1377,7 +1617,7 @@ class Change {
|
|
|
1377
1617
|
}
|
|
1378
1618
|
}
|
|
1379
1619
|
|
|
1380
|
-
//
|
|
1620
|
+
// apps/cli/modules/add/app/get-changes.use-case.ts
|
|
1381
1621
|
class GetChangesUseCase {
|
|
1382
1622
|
gitRepository;
|
|
1383
1623
|
constructor(gitRepository) {
|
|
@@ -1404,7 +1644,7 @@ class GetChangesUseCase {
|
|
|
1404
1644
|
}
|
|
1405
1645
|
}
|
|
1406
1646
|
|
|
1407
|
-
//
|
|
1647
|
+
// apps/cli/modules/add/app/stage-changes.use-case.ts
|
|
1408
1648
|
class StageChangesUseCase {
|
|
1409
1649
|
gitRepository;
|
|
1410
1650
|
constructor(gitRepository) {
|
|
@@ -1417,16 +1657,19 @@ class StageChangesUseCase {
|
|
|
1417
1657
|
}
|
|
1418
1658
|
}
|
|
1419
1659
|
|
|
1420
|
-
//
|
|
1660
|
+
// apps/cli/modules/add/infra/bun-git.repository.ts
|
|
1421
1661
|
var {$: $2 } = globalThis.Bun;
|
|
1422
|
-
|
|
1423
1662
|
class BunGitRepository {
|
|
1424
1663
|
async getEntries() {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1664
|
+
try {
|
|
1665
|
+
const output = await $2`git status --porcelain -z`.quiet().text();
|
|
1666
|
+
if (!output.trim()) {
|
|
1667
|
+
return [];
|
|
1668
|
+
}
|
|
1669
|
+
return output.split("\x00").filter(Boolean);
|
|
1670
|
+
} catch {
|
|
1671
|
+
throw new ServerError("Git status failed", "Could not retrieve repository status");
|
|
1428
1672
|
}
|
|
1429
|
-
return output.split("\x00").filter(Boolean);
|
|
1430
1673
|
}
|
|
1431
1674
|
async stageFiles(files) {
|
|
1432
1675
|
if (files.length === 0)
|
|
@@ -1436,12 +1679,12 @@ class BunGitRepository {
|
|
|
1436
1679
|
});
|
|
1437
1680
|
const exitCode = await proc.exited;
|
|
1438
1681
|
if (exitCode !== 0) {
|
|
1439
|
-
throw new
|
|
1682
|
+
throw new ServerError("Git add failed", `Failed to stage ${files.length} file(s)`);
|
|
1440
1683
|
}
|
|
1441
1684
|
}
|
|
1442
1685
|
}
|
|
1443
1686
|
|
|
1444
|
-
//
|
|
1687
|
+
// apps/cli/modules/add/main.ts
|
|
1445
1688
|
async function addCommand() {
|
|
1446
1689
|
const gitRepository = new BunGitRepository;
|
|
1447
1690
|
const getChangesUseCase = new GetChangesUseCase(gitRepository);
|
|
@@ -1450,7 +1693,405 @@ async function addCommand() {
|
|
|
1450
1693
|
await addCommand2.execute();
|
|
1451
1694
|
}
|
|
1452
1695
|
|
|
1453
|
-
//
|
|
1696
|
+
// apps/cli/alias.ts
|
|
1697
|
+
var ALIASES = [
|
|
1698
|
+
{ name: "gs", command: "status -sb", kind: "git" },
|
|
1699
|
+
{
|
|
1700
|
+
name: "gl",
|
|
1701
|
+
command: "log --oneline --decorate --graph --all -n 20",
|
|
1702
|
+
kind: "git"
|
|
1703
|
+
},
|
|
1704
|
+
{ name: "gll", command: "log --stat", kind: "git" },
|
|
1705
|
+
{ name: "gd", command: "diff --word-diff=color", kind: "git" },
|
|
1706
|
+
{ name: "gds", command: "diff --staged --word-diff=color", kind: "git" },
|
|
1707
|
+
{ name: "ga", command: "add", kind: "glinter" },
|
|
1708
|
+
{ name: "gaa", command: "add -A", kind: "git" },
|
|
1709
|
+
{ name: "gc", command: "commit", kind: "glinter" },
|
|
1710
|
+
{ name: "gcm", command: "commit -m", kind: "git" },
|
|
1711
|
+
{ name: "gca", command: "commit --amend", kind: "git" },
|
|
1712
|
+
{ name: "gcan", command: "commit --amend --no-edit", kind: "git" },
|
|
1713
|
+
{ name: "gb", command: "branch", kind: "git" },
|
|
1714
|
+
{ name: "gba", command: "branch -a", kind: "git" },
|
|
1715
|
+
{ name: "gco", command: "switch", kind: "glinter" },
|
|
1716
|
+
{ name: "gcb", command: "checkout -b", kind: "git" },
|
|
1717
|
+
{ name: "gpl", command: "pull", kind: "git" },
|
|
1718
|
+
{ name: "gplr", command: "pull --rebase", kind: "git" },
|
|
1719
|
+
{ name: "gp", command: "push", kind: "git" },
|
|
1720
|
+
{ name: "ggpush", command: "push origin HEAD", kind: "git" },
|
|
1721
|
+
{ name: "gpf", command: "push --force-with-lease", kind: "git" },
|
|
1722
|
+
{ name: "gst", command: "stash", kind: "git" },
|
|
1723
|
+
{ name: "gstp", command: "stash pop", kind: "git" },
|
|
1724
|
+
{ name: "gstl", command: "stash list", kind: "git" },
|
|
1725
|
+
{ name: "gcl", command: "clean -fd", kind: "git" },
|
|
1726
|
+
{ name: "grh", command: "reset --hard", kind: "git" }
|
|
1727
|
+
];
|
|
1728
|
+
|
|
1729
|
+
// apps/cli/modules/alias/main.ts
|
|
1730
|
+
function resolveAlias(name, command) {
|
|
1731
|
+
return { name, value: command };
|
|
1732
|
+
}
|
|
1733
|
+
function printAliases({
|
|
1734
|
+
title = "The following aliases are configured by setup:"
|
|
1735
|
+
} = {}) {
|
|
1736
|
+
console.log(YELLOW({ text: `
|
|
1737
|
+
${title}
|
|
1738
|
+
` }));
|
|
1739
|
+
for (const alias of ALIASES) {
|
|
1740
|
+
const tag = alias.kind === "glinter" ? MAGENTA({ text: "[glinter]" }) : BLUE({ text: "[git] " });
|
|
1741
|
+
const { name, value } = resolveAlias(alias.name, alias.command);
|
|
1742
|
+
console.log(` ${tag} ${GREEN({ text: name.padEnd(8) })} \u2192 ${value}`);
|
|
1743
|
+
}
|
|
1744
|
+
console.log("");
|
|
1745
|
+
}
|
|
1746
|
+
async function aliasCommand() {
|
|
1747
|
+
printAliases();
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// apps/cli/modules/commit/app/commit.use-case.ts
|
|
1751
|
+
class CommitUseCase {
|
|
1752
|
+
commitRepository;
|
|
1753
|
+
constructor(commitRepository) {
|
|
1754
|
+
this.commitRepository = commitRepository;
|
|
1755
|
+
}
|
|
1756
|
+
async execute({ message }) {
|
|
1757
|
+
await this.commitRepository.commit(message);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// apps/cli/commit-options.ts
|
|
1762
|
+
var commitTypeOptions = [
|
|
1763
|
+
{
|
|
1764
|
+
value: "feat",
|
|
1765
|
+
label: `${GREEN({ text: "feat" })}: A new feature`
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
value: "fix",
|
|
1769
|
+
label: `${GREEN({ text: "fix" })}: A bug fix`
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
value: "chore",
|
|
1773
|
+
label: `${GREEN({ text: "chore" })}: Routine maintenance`
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
value: "docs",
|
|
1777
|
+
label: `${GREEN({ text: "docs" })}: Documentation updates`
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
value: "refactor",
|
|
1781
|
+
label: `${GREEN({ text: "refactor" })}: Code changes without behavior change`
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
value: "test",
|
|
1785
|
+
label: `${GREEN({ text: "test" })}: Add or update tests`
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
value: "perf",
|
|
1789
|
+
label: `${GREEN({ text: "perf" })}: Performance improvements`
|
|
1790
|
+
},
|
|
1791
|
+
{
|
|
1792
|
+
value: "style",
|
|
1793
|
+
label: `${GREEN({ text: "style" })}: Formatting or style-only changes`
|
|
1794
|
+
}
|
|
1795
|
+
];
|
|
1796
|
+
|
|
1797
|
+
// apps/cli/utils/input.ts
|
|
1798
|
+
async function Input({
|
|
1799
|
+
message,
|
|
1800
|
+
placeholder
|
|
1801
|
+
}) {
|
|
1802
|
+
const value = await Ot({
|
|
1803
|
+
message,
|
|
1804
|
+
placeholder,
|
|
1805
|
+
validate(input) {
|
|
1806
|
+
if (!input?.trim()) {
|
|
1807
|
+
return "Commit message is required.";
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1811
|
+
if (q(value)) {
|
|
1812
|
+
pt("Operation cancelled.");
|
|
1813
|
+
process.exit(0);
|
|
1814
|
+
}
|
|
1815
|
+
return value.trim();
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// apps/cli/utils/select.ts
|
|
1819
|
+
async function Select({
|
|
1820
|
+
message,
|
|
1821
|
+
options
|
|
1822
|
+
}) {
|
|
1823
|
+
const selected = await _t({
|
|
1824
|
+
message,
|
|
1825
|
+
options
|
|
1826
|
+
});
|
|
1827
|
+
if (q(selected)) {
|
|
1828
|
+
pt("Operation cancelled.");
|
|
1829
|
+
process.exit(0);
|
|
1830
|
+
}
|
|
1831
|
+
return selected;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// apps/cli/modules/commit/app/commit-command.ts
|
|
1835
|
+
class CommitCommand {
|
|
1836
|
+
commitUseCase;
|
|
1837
|
+
constructor(commitUseCase) {
|
|
1838
|
+
this.commitUseCase = commitUseCase;
|
|
1839
|
+
}
|
|
1840
|
+
async execute() {
|
|
1841
|
+
try {
|
|
1842
|
+
const commitType = await Select({
|
|
1843
|
+
message: "Select the commit type.",
|
|
1844
|
+
options: commitTypeOptions
|
|
1845
|
+
});
|
|
1846
|
+
const commitDescription = await Input({
|
|
1847
|
+
message: "Write your commit message.",
|
|
1848
|
+
placeholder: "e.g. add interactive commit flow"
|
|
1849
|
+
});
|
|
1850
|
+
await this.commitUseCase.execute({
|
|
1851
|
+
message: `${commitType}: ${commitDescription}`
|
|
1852
|
+
});
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
errorHandler(error);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// apps/cli/modules/commit/infra/bun-commit.repository.ts
|
|
1860
|
+
class BunCommitRepository {
|
|
1861
|
+
async commit(message) {
|
|
1862
|
+
const proc = Bun.spawn(["git", "commit", "-m", message], {
|
|
1863
|
+
stdio: ["inherit", "inherit", "inherit"]
|
|
1864
|
+
});
|
|
1865
|
+
const exitCode = await proc.exited;
|
|
1866
|
+
if (exitCode !== 0) {
|
|
1867
|
+
throw new ServerError("Git commit failed", "Could not create commit from interactive prompt");
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// apps/cli/modules/commit/main.ts
|
|
1873
|
+
async function commitCommand() {
|
|
1874
|
+
const commitRepository = new BunCommitRepository;
|
|
1875
|
+
const commitUseCase = new CommitUseCase(commitRepository);
|
|
1876
|
+
const commitCommand2 = new CommitCommand(commitUseCase);
|
|
1877
|
+
await commitCommand2.execute();
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// apps/cli/modules/setup/app/setup-aliases.use-case.ts
|
|
1881
|
+
class SetupAliasesUseCase {
|
|
1882
|
+
aliasRepository;
|
|
1883
|
+
constructor(aliasRepository) {
|
|
1884
|
+
this.aliasRepository = aliasRepository;
|
|
1885
|
+
}
|
|
1886
|
+
async execute() {
|
|
1887
|
+
for (const alias of ALIASES) {
|
|
1888
|
+
const { name, value } = this.resolveAlias(alias.name, alias.command, alias.kind);
|
|
1889
|
+
await this.aliasRepository.setAlias(name, value);
|
|
1890
|
+
}
|
|
1891
|
+
return { total: ALIASES.length };
|
|
1892
|
+
}
|
|
1893
|
+
resolveAlias(name, command, kind) {
|
|
1894
|
+
const aliasCommand2 = kind === "git" ? `git ${command}` : `g ${command}`;
|
|
1895
|
+
return { name, value: aliasCommand2 };
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// apps/cli/utils/confirm.ts
|
|
1900
|
+
async function Confirm({
|
|
1901
|
+
message,
|
|
1902
|
+
cancelMessage,
|
|
1903
|
+
exitOnCancel = true
|
|
1904
|
+
}) {
|
|
1905
|
+
const confirmed = await ot2({
|
|
1906
|
+
message
|
|
1907
|
+
});
|
|
1908
|
+
if (q(confirmed)) {
|
|
1909
|
+
if (exitOnCancel) {
|
|
1910
|
+
pt(cancelMessage ?? "Operation cancelled.");
|
|
1911
|
+
process.exit(0);
|
|
1912
|
+
}
|
|
1913
|
+
return null;
|
|
1914
|
+
}
|
|
1915
|
+
return confirmed;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// apps/cli/utils/intro.ts
|
|
1919
|
+
function Intro(message) {
|
|
1920
|
+
mt(message);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// apps/cli/utils/outro.ts
|
|
1924
|
+
function Outro(message) {
|
|
1925
|
+
gt(message);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// apps/cli/utils/spinner.ts
|
|
1929
|
+
function Spinner() {
|
|
1930
|
+
return fe();
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// apps/cli/modules/setup/app/setup-command.ts
|
|
1934
|
+
class SetupCommand {
|
|
1935
|
+
setupAliasesUseCase;
|
|
1936
|
+
constructor(setupAliasesUseCase) {
|
|
1937
|
+
this.setupAliasesUseCase = setupAliasesUseCase;
|
|
1938
|
+
}
|
|
1939
|
+
async execute() {
|
|
1940
|
+
try {
|
|
1941
|
+
Intro(MAGENTA({ text: " Glinter Setup " }));
|
|
1942
|
+
printAliases({ title: "The following aliases will be set globally:" });
|
|
1943
|
+
const confirmed = await Confirm({
|
|
1944
|
+
message: "Apply these aliases to your global git config?",
|
|
1945
|
+
exitOnCancel: false
|
|
1946
|
+
});
|
|
1947
|
+
if (!confirmed) {
|
|
1948
|
+
Outro("Setup cancelled.");
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
const spinner = Spinner();
|
|
1952
|
+
spinner.start("Writing aliases...");
|
|
1953
|
+
const { total } = await this.setupAliasesUseCase.execute();
|
|
1954
|
+
console.log("");
|
|
1955
|
+
spinner.stop(`${CHECK({ text: `${total} aliases configured successfully.` })}`);
|
|
1956
|
+
Outro(`${GREEN({ text: "GLINTER" })}, if you like the project, give a star on github: https://github.com/jannael/glinter`);
|
|
1957
|
+
console.log("");
|
|
1958
|
+
console.log(MAGENTA({ text: "To use the aliases, please restart your terminal." }));
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
errorHandler(error);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// apps/cli/modules/setup/infra/bun-alias-unix.ts
|
|
1966
|
+
import fs from "fs";
|
|
1967
|
+
import os from "os";
|
|
1968
|
+
import path from "path";
|
|
1969
|
+
|
|
1970
|
+
class BunAliasUnix {
|
|
1971
|
+
async setAlias(name, value) {
|
|
1972
|
+
const profilePath = this.getUnixProfilePath();
|
|
1973
|
+
fs.mkdirSync(path.dirname(profilePath), { recursive: true });
|
|
1974
|
+
const current = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, "utf8") : "";
|
|
1975
|
+
const updated = this.upsertUnixProfileAlias(current, name, value);
|
|
1976
|
+
fs.writeFileSync(profilePath, updated);
|
|
1977
|
+
}
|
|
1978
|
+
upsertUnixProfileAlias(content, name, value) {
|
|
1979
|
+
const escapedName = this.escapeRegex(name);
|
|
1980
|
+
const escapedValue = this.escapeSingleQuotes(value);
|
|
1981
|
+
const aliasLine = `alias ${name}='${escapedValue}'`;
|
|
1982
|
+
const aliasRegex = new RegExp(`^alias ${escapedName}=.*\\r?\\n?`, "gm");
|
|
1983
|
+
const cleaned = content.replace(aliasRegex, "").trimEnd();
|
|
1984
|
+
const prefix = cleaned.length > 0 ? `${cleaned}
|
|
1985
|
+
` : "";
|
|
1986
|
+
return `${prefix}${aliasLine}
|
|
1987
|
+
`;
|
|
1988
|
+
}
|
|
1989
|
+
getUnixProfilePath() {
|
|
1990
|
+
const home = os.homedir();
|
|
1991
|
+
const shellName = path.basename(process.env.SHELL ?? "");
|
|
1992
|
+
const preferredProfile = shellName === "zsh" ? ".zshrc" : shellName === "bash" ? ".bashrc" : ".profile";
|
|
1993
|
+
const candidates = [preferredProfile, ".bashrc", ".zshrc", ".profile"];
|
|
1994
|
+
const uniqueCandidates = [...new Set(candidates)];
|
|
1995
|
+
for (const candidate of uniqueCandidates) {
|
|
1996
|
+
const candidatePath = path.join(home, candidate);
|
|
1997
|
+
if (fs.existsSync(candidatePath)) {
|
|
1998
|
+
return candidatePath;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
return path.join(home, preferredProfile);
|
|
2002
|
+
}
|
|
2003
|
+
escapeRegex(value) {
|
|
2004
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2005
|
+
}
|
|
2006
|
+
escapeSingleQuotes(value) {
|
|
2007
|
+
return value.replace(/'/g, `'"'"'`);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// apps/cli/modules/setup/infra/bun-alias-windows.ts
|
|
2012
|
+
import fs2 from "fs";
|
|
2013
|
+
import os2 from "os";
|
|
2014
|
+
import path2 from "path";
|
|
2015
|
+
|
|
2016
|
+
class BunAliasWindows {
|
|
2017
|
+
async setAlias(name, value) {
|
|
2018
|
+
const psProfilePath = this.getWindowsProfilePath();
|
|
2019
|
+
fs2.mkdirSync(path2.dirname(psProfilePath), { recursive: true });
|
|
2020
|
+
const current = fs2.existsSync(psProfilePath) ? fs2.readFileSync(psProfilePath, "utf8") : "";
|
|
2021
|
+
const updated = this.upsertWindowsProfileFunction(current, name, value);
|
|
2022
|
+
fs2.writeFileSync(psProfilePath, updated);
|
|
2023
|
+
}
|
|
2024
|
+
upsertWindowsProfileFunction(content, name, value) {
|
|
2025
|
+
const escapedName = this.escapeRegex(name);
|
|
2026
|
+
const removeAliasLine = `if (Get-Alias -Name ${name} -ErrorAction SilentlyContinue) { Remove-Item Alias:${name} -Force }`;
|
|
2027
|
+
const functionLine = `function ${name} { ${value} @args }`;
|
|
2028
|
+
const removeAliasRegex = new RegExp(`^if \\(Get-Alias -Name ${escapedName} -ErrorAction SilentlyContinue\\) \\{ Remove-Item Alias:${escapedName} -Force \\}\\r?\\n?`, "gm");
|
|
2029
|
+
const functionRegex = new RegExp(`^function ${escapedName} \\{[^\\r\\n]*\\}\\r?\\n?`, "gm");
|
|
2030
|
+
const cleaned = content.replace(removeAliasRegex, "").replace(functionRegex, "").trimEnd();
|
|
2031
|
+
const prefix = cleaned.length > 0 ? `${cleaned}
|
|
2032
|
+
` : "";
|
|
2033
|
+
return `${prefix}${removeAliasLine}
|
|
2034
|
+
${functionLine}
|
|
2035
|
+
`;
|
|
2036
|
+
}
|
|
2037
|
+
escapeRegex(value) {
|
|
2038
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2039
|
+
}
|
|
2040
|
+
getWindowsProfilePath() {
|
|
2041
|
+
return path2.join(this.getWindowsDocumentsPath(), "PowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
2042
|
+
}
|
|
2043
|
+
getWindowsDocumentsPath() {
|
|
2044
|
+
const proc = Bun.spawnSync([
|
|
2045
|
+
"powershell",
|
|
2046
|
+
"-NoProfile",
|
|
2047
|
+
"-Command",
|
|
2048
|
+
"[Environment]::GetFolderPath('MyDocuments')"
|
|
2049
|
+
], {
|
|
2050
|
+
stdout: "pipe",
|
|
2051
|
+
stderr: "pipe"
|
|
2052
|
+
});
|
|
2053
|
+
if (proc.exitCode === 0) {
|
|
2054
|
+
const docsPath = new TextDecoder().decode(proc.stdout).trim();
|
|
2055
|
+
if (docsPath.length > 0) {
|
|
2056
|
+
return docsPath;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return path2.join(os2.homedir(), "Documents");
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// apps/cli/modules/setup/infra/bun-alias.repository.ts
|
|
2064
|
+
class BunAliasRepository {
|
|
2065
|
+
getOS() {
|
|
2066
|
+
return process.platform === "win32" ? "windows" : "unix";
|
|
2067
|
+
}
|
|
2068
|
+
async setAlias(name, value) {
|
|
2069
|
+
try {
|
|
2070
|
+
const system = this.getOS();
|
|
2071
|
+
if (system === "windows") {
|
|
2072
|
+
const bunAliasWindows = new BunAliasWindows;
|
|
2073
|
+
await bunAliasWindows.setAlias(name, value);
|
|
2074
|
+
}
|
|
2075
|
+
if (system === "unix") {
|
|
2076
|
+
const bunAliasUnix = new BunAliasUnix;
|
|
2077
|
+
await bunAliasUnix.setAlias(name, value);
|
|
2078
|
+
}
|
|
2079
|
+
console.log(` ${MAGENTA({ text: name.padEnd(8) })} set ${GREEN({ text: "successfully".padStart(10) })}`);
|
|
2080
|
+
} catch {
|
|
2081
|
+
throw new ServerError("Unexpected execution error", `Failed to set alias: ${name}`);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// apps/cli/modules/setup/main.ts
|
|
2087
|
+
async function setupCommand() {
|
|
2088
|
+
const aliasRepository = new BunAliasRepository;
|
|
2089
|
+
const setupAliasesUseCase = new SetupAliasesUseCase(aliasRepository);
|
|
2090
|
+
const setupCommand2 = new SetupCommand(setupAliasesUseCase);
|
|
2091
|
+
await setupCommand2.execute();
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// apps/cli/modules/switch/domain/branch.ts
|
|
1454
2095
|
class Branch {
|
|
1455
2096
|
props;
|
|
1456
2097
|
constructor(props) {
|
|
@@ -1486,7 +2127,7 @@ class Branch {
|
|
|
1486
2127
|
}
|
|
1487
2128
|
}
|
|
1488
2129
|
|
|
1489
|
-
//
|
|
2130
|
+
// apps/cli/modules/switch/app/get-branches.use-case.ts
|
|
1490
2131
|
class GetBranchesUseCase {
|
|
1491
2132
|
branchRepository;
|
|
1492
2133
|
constructor(branchRepository) {
|
|
@@ -1499,7 +2140,7 @@ class GetBranchesUseCase {
|
|
|
1499
2140
|
}
|
|
1500
2141
|
}
|
|
1501
2142
|
|
|
1502
|
-
//
|
|
2143
|
+
// apps/cli/modules/switch/app/switch-branch.use-case.ts
|
|
1503
2144
|
class SwitchBranch {
|
|
1504
2145
|
branchRepository;
|
|
1505
2146
|
constructor(branchRepository) {
|
|
@@ -1510,23 +2151,7 @@ class SwitchBranch {
|
|
|
1510
2151
|
}
|
|
1511
2152
|
}
|
|
1512
2153
|
|
|
1513
|
-
//
|
|
1514
|
-
async function Select({
|
|
1515
|
-
message,
|
|
1516
|
-
options
|
|
1517
|
-
}) {
|
|
1518
|
-
const selected = await _t({
|
|
1519
|
-
message,
|
|
1520
|
-
options
|
|
1521
|
-
});
|
|
1522
|
-
if (q(selected)) {
|
|
1523
|
-
pt("Operation cancelled.");
|
|
1524
|
-
process.exit(0);
|
|
1525
|
-
}
|
|
1526
|
-
return selected;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
// src/modules/switch/app/switch-command.ts
|
|
2154
|
+
// apps/cli/modules/switch/app/switch-command.ts
|
|
1530
2155
|
class SwitchCommand {
|
|
1531
2156
|
getBranchesUseCase;
|
|
1532
2157
|
switchBranch;
|
|
@@ -1535,49 +2160,56 @@ class SwitchCommand {
|
|
|
1535
2160
|
this.switchBranch = switchBranch;
|
|
1536
2161
|
}
|
|
1537
2162
|
async execute() {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
const options = [];
|
|
1544
|
-
for (const branch of branches) {
|
|
1545
|
-
let label = branch.name;
|
|
1546
|
-
let value = branch.name;
|
|
1547
|
-
if (branch.isHeadBranch)
|
|
1548
|
-
continue;
|
|
1549
|
-
if (branch.isCurrentBranch) {
|
|
1550
|
-
console.log(`${CHECK} Current branch: ${branch.name}`);
|
|
1551
|
-
continue;
|
|
2163
|
+
try {
|
|
2164
|
+
const branches = await this.getBranchesUseCase.execute();
|
|
2165
|
+
if (branches.length === 0) {
|
|
2166
|
+
console.log(`${CHECK({ text: "No branches found." })}`);
|
|
2167
|
+
return;
|
|
1552
2168
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
2169
|
+
const options = [];
|
|
2170
|
+
for (const branch of branches) {
|
|
2171
|
+
let label = branch.name;
|
|
2172
|
+
let value = branch.name;
|
|
2173
|
+
if (branch.isHeadBranch)
|
|
2174
|
+
continue;
|
|
2175
|
+
if (branch.isCurrentBranch) {
|
|
2176
|
+
console.log(`${CHECK({ text: `Current branch: ${branch.name}` })}`);
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
if (branch.isRemoteBranch) {
|
|
2180
|
+
label = branch.name.replace("remotes/origin/", GREEN({ text: "remote branch: " }));
|
|
2181
|
+
value = branch.name.replace("remotes/origin/", "");
|
|
2182
|
+
}
|
|
2183
|
+
options.push({
|
|
2184
|
+
value,
|
|
2185
|
+
label
|
|
2186
|
+
});
|
|
1556
2187
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
2188
|
+
const selectedBranch = await Select({
|
|
2189
|
+
message: "Select the branch you want to switch to.",
|
|
2190
|
+
options
|
|
1560
2191
|
});
|
|
2192
|
+
await this.switchBranch.execute({ branch: selectedBranch });
|
|
2193
|
+
} catch (error) {
|
|
2194
|
+
errorHandler(error);
|
|
1561
2195
|
}
|
|
1562
|
-
const selectedBranch = await Select({
|
|
1563
|
-
message: "Select the branch you want to switch to.",
|
|
1564
|
-
options
|
|
1565
|
-
});
|
|
1566
|
-
await this.switchBranch.execute({ branch: selectedBranch });
|
|
1567
2196
|
}
|
|
1568
2197
|
}
|
|
1569
2198
|
|
|
1570
|
-
//
|
|
2199
|
+
// apps/cli/modules/switch/infra/bun-switch-repository.ts
|
|
1571
2200
|
var {$: $3 } = globalThis.Bun;
|
|
1572
|
-
|
|
1573
2201
|
class BunSwitchRepository {
|
|
1574
2202
|
async getBranches() {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
2203
|
+
try {
|
|
2204
|
+
const output = await $3`git branch -a`.quiet().text();
|
|
2205
|
+
if (!output.trim()) {
|
|
2206
|
+
return [];
|
|
2207
|
+
}
|
|
2208
|
+
return output.split(`
|
|
1580
2209
|
`).map((branch) => branch.trim());
|
|
2210
|
+
} catch {
|
|
2211
|
+
throw new ServerError("Git branch failed", "Could not retrieve branches");
|
|
2212
|
+
}
|
|
1581
2213
|
}
|
|
1582
2214
|
async switchBranch({ branch }) {
|
|
1583
2215
|
const proc = Bun.spawn(["git", "checkout", branch], {
|
|
@@ -1585,12 +2217,12 @@ class BunSwitchRepository {
|
|
|
1585
2217
|
});
|
|
1586
2218
|
const exitCode = await proc.exited;
|
|
1587
2219
|
if (exitCode !== 0) {
|
|
1588
|
-
throw new
|
|
2220
|
+
throw new ServerError("Git checkout failed", `Failed to switch to branch ${branch}`);
|
|
1589
2221
|
}
|
|
1590
2222
|
}
|
|
1591
2223
|
}
|
|
1592
2224
|
|
|
1593
|
-
//
|
|
2225
|
+
// apps/cli/modules/switch/main.ts
|
|
1594
2226
|
async function switchCommand() {
|
|
1595
2227
|
const switchRepo = new BunSwitchRepository;
|
|
1596
2228
|
const switchBranchUseCase = new SwitchBranch(switchRepo);
|
|
@@ -1599,12 +2231,18 @@ async function switchCommand() {
|
|
|
1599
2231
|
await switchCommand2.execute();
|
|
1600
2232
|
}
|
|
1601
2233
|
|
|
1602
|
-
//
|
|
2234
|
+
// apps/cli/index.ts
|
|
1603
2235
|
var args = Bun.argv.slice(2);
|
|
1604
2236
|
if (args[0] === "add" && !args[1])
|
|
1605
2237
|
await addCommand();
|
|
2238
|
+
else if (args[0] === "commit" && !args[1])
|
|
2239
|
+
await commitCommand();
|
|
1606
2240
|
else if (args[0] === "switch" && !args[1])
|
|
1607
2241
|
await switchCommand();
|
|
2242
|
+
else if (args[0] === "alias" && !args[1])
|
|
2243
|
+
await aliasCommand();
|
|
2244
|
+
else if (args[0] === "setup")
|
|
2245
|
+
await setupCommand();
|
|
1608
2246
|
else {
|
|
1609
2247
|
const proc = Bun.spawn(["git", ...args], {
|
|
1610
2248
|
stdio: ["inherit", "inherit", "inherit"]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jannael/glinter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A high-performance, transparent Git wrapper with interactive staging",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"name": "Jannael",
|
|
14
14
|
"url": "https://github.com/jannael"
|
|
15
15
|
},
|
|
16
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://glinter.jannael.com",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/jannael/glinter/issues"
|
|
19
19
|
},
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
".": "./dist/index.js"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
37
|
+
"build:cli": "bun build ./apps/cli/index.ts --outfile=./dist/index.js --target=bun",
|
|
38
|
+
"prepublishOnly": "bun run build:cli",
|
|
39
|
+
|
|
40
40
|
"test": "vitest",
|
|
41
41
|
"lint": "biome check",
|
|
42
|
-
"lint:fix": "biome format --write"
|
|
42
|
+
"lint:fix": "biome format --write",
|
|
43
|
+
"type-check": "bunx tsc --noEmit"
|
|
43
44
|
},
|
|
44
45
|
"bin": {
|
|
45
46
|
"g": "./dist/index.js"
|
|
@@ -59,4 +60,4 @@
|
|
|
59
60
|
"dependencies": {
|
|
60
61
|
"@clack/prompts": "1.2.0"
|
|
61
62
|
}
|
|
62
|
-
}
|
|
63
|
+
}
|