@mehmetsagir/git-ai 0.0.8 ā 0.0.18
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 +91 -319
- package/dist/commit.d.ts +1 -4
- package/dist/commit.d.ts.map +1 -1
- package/dist/commit.js +214 -208
- package/dist/commit.js.map +1 -1
- package/dist/config.d.ts +0 -39
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -95
- package/dist/config.js.map +1 -1
- package/dist/git.d.ts +19 -51
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +137 -269
- package/dist/git.js.map +1 -1
- package/dist/index.js +54 -109
- package/dist/index.js.map +1 -1
- package/dist/openai.d.ts +2 -21
- package/dist/openai.d.ts.map +1 -1
- package/dist/openai.js +17 -96
- package/dist/openai.js.map +1 -1
- package/dist/prompts.d.ts +2 -16
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +32 -146
- package/dist/prompts.js.map +1 -1
- package/dist/reset.d.ts +0 -3
- package/dist/reset.d.ts.map +1 -1
- package/dist/reset.js +10 -20
- package/dist/reset.js.map +1 -1
- package/dist/setup.d.ts +0 -3
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +28 -156
- package/dist/setup.js.map +1 -1
- package/dist/stash.d.ts +2 -0
- package/dist/stash.d.ts.map +1 -0
- package/dist/stash.js +818 -0
- package/dist/stash.js.map +1 -0
- package/dist/types.d.ts +9 -23
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/hunk-parser.d.ts +22 -0
- package/dist/utils/hunk-parser.d.ts.map +1 -0
- package/dist/utils/hunk-parser.js +155 -0
- package/dist/utils/hunk-parser.js.map +1 -0
- package/package.json +12 -8
- package/dist/add.d.ts +0 -5
- package/dist/add.d.ts.map +0 -1
- package/dist/add.js +0 -98
- package/dist/add.js.map +0 -1
- package/dist/commit-processor.d.ts +0 -6
- package/dist/commit-processor.d.ts.map +0 -1
- package/dist/commit-processor.js +0 -226
- package/dist/commit-processor.js.map +0 -1
- package/dist/update.d.ts +0 -9
- package/dist/update.d.ts.map +0 -1
- package/dist/update.js +0 -69
- package/dist/update.js.map +0 -1
- package/dist/user-management.d.ts +0 -10
- package/dist/user-management.d.ts.map +0 -1
- package/dist/user-management.js +0 -175
- package/dist/user-management.js.map +0 -1
- package/dist/users.d.ts +0 -9
- package/dist/users.d.ts.map +0 -1
- package/dist/users.js +0 -129
- package/dist/users.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -24
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -81
- package/dist/utils/validation.js.map +0 -1
package/dist/stash.js
ADDED
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.runStashViewer = runStashViewer;
|
|
40
|
+
const http = __importStar(require("http"));
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const git = __importStar(require("./git"));
|
|
44
|
+
const PORT = 3847;
|
|
45
|
+
function getHtml(data) {
|
|
46
|
+
const stashesJson = JSON.stringify(data.stashes);
|
|
47
|
+
const diffsJson = {};
|
|
48
|
+
const filesJson = {};
|
|
49
|
+
data.diffs.forEach((v, k) => { diffsJson[k] = v; });
|
|
50
|
+
data.files.forEach((v, k) => { filesJson[k] = v; });
|
|
51
|
+
return `<!DOCTYPE html>
|
|
52
|
+
<html>
|
|
53
|
+
<head>
|
|
54
|
+
<meta charset="UTF-8">
|
|
55
|
+
<title>Git Stash Viewer</title>
|
|
56
|
+
<style>
|
|
57
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
58
|
+
body {
|
|
59
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
60
|
+
background: #1e1e1e;
|
|
61
|
+
color: #cccccc;
|
|
62
|
+
height: 100vh;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
.container { display: flex; height: 100vh; }
|
|
66
|
+
|
|
67
|
+
/* Sidebar */
|
|
68
|
+
.sidebar {
|
|
69
|
+
width: 300px;
|
|
70
|
+
background: #252526;
|
|
71
|
+
border-right: 1px solid #3c3c3c;
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
}
|
|
76
|
+
.sidebar-header {
|
|
77
|
+
padding: 12px 16px;
|
|
78
|
+
background: #2d2d2d;
|
|
79
|
+
border-bottom: 1px solid #3c3c3c;
|
|
80
|
+
font-size: 11px;
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
letter-spacing: 0.5px;
|
|
83
|
+
color: #bbbbbb;
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
}
|
|
88
|
+
.back-btn {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 6px;
|
|
92
|
+
background: none;
|
|
93
|
+
border: none;
|
|
94
|
+
color: #58a6ff;
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
font-size: 12px;
|
|
97
|
+
padding: 4px 8px;
|
|
98
|
+
border-radius: 4px;
|
|
99
|
+
transition: background 0.1s;
|
|
100
|
+
}
|
|
101
|
+
.back-btn:hover { background: #3c3c3c; }
|
|
102
|
+
.back-btn svg {
|
|
103
|
+
width: 14px;
|
|
104
|
+
height: 14px;
|
|
105
|
+
}
|
|
106
|
+
.stash-title {
|
|
107
|
+
color: #4ec9b0;
|
|
108
|
+
font-size: 12px;
|
|
109
|
+
font-weight: 500;
|
|
110
|
+
text-transform: none;
|
|
111
|
+
letter-spacing: normal;
|
|
112
|
+
}
|
|
113
|
+
.sidebar-footer {
|
|
114
|
+
padding: 14px 16px;
|
|
115
|
+
border-top: 1px solid #3c3c3c;
|
|
116
|
+
background: #1e1e1e;
|
|
117
|
+
text-align: center;
|
|
118
|
+
}
|
|
119
|
+
.sidebar-footer-brand {
|
|
120
|
+
font-size: 13px;
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
color: #e1e1e1;
|
|
123
|
+
margin-bottom: 6px;
|
|
124
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
125
|
+
}
|
|
126
|
+
.sidebar-footer a {
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: 6px;
|
|
130
|
+
color: #6e7681;
|
|
131
|
+
text-decoration: none;
|
|
132
|
+
font-size: 11px;
|
|
133
|
+
transition: color 0.15s;
|
|
134
|
+
}
|
|
135
|
+
.sidebar-footer a:hover {
|
|
136
|
+
color: #58a6ff;
|
|
137
|
+
}
|
|
138
|
+
.sidebar-footer a svg {
|
|
139
|
+
width: 14px;
|
|
140
|
+
height: 14px;
|
|
141
|
+
}
|
|
142
|
+
.stash-actions {
|
|
143
|
+
display: flex;
|
|
144
|
+
gap: 8px;
|
|
145
|
+
padding: 12px 16px;
|
|
146
|
+
background: #2d2d2d;
|
|
147
|
+
border-bottom: 1px solid #3c3c3c;
|
|
148
|
+
}
|
|
149
|
+
.stash-actions button {
|
|
150
|
+
flex: 1;
|
|
151
|
+
padding: 8px 12px;
|
|
152
|
+
border: none;
|
|
153
|
+
border-radius: 4px;
|
|
154
|
+
font-size: 12px;
|
|
155
|
+
font-weight: 500;
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
gap: 6px;
|
|
161
|
+
transition: background 0.15s, opacity 0.15s;
|
|
162
|
+
}
|
|
163
|
+
.stash-actions button:disabled {
|
|
164
|
+
opacity: 0.5;
|
|
165
|
+
cursor: not-allowed;
|
|
166
|
+
}
|
|
167
|
+
.stash-actions button svg {
|
|
168
|
+
width: 14px;
|
|
169
|
+
height: 14px;
|
|
170
|
+
}
|
|
171
|
+
.btn-apply {
|
|
172
|
+
background: #238636;
|
|
173
|
+
color: #fff;
|
|
174
|
+
}
|
|
175
|
+
.btn-apply:hover:not(:disabled) {
|
|
176
|
+
background: #2ea043;
|
|
177
|
+
}
|
|
178
|
+
.btn-delete {
|
|
179
|
+
background: #da3633;
|
|
180
|
+
color: #fff;
|
|
181
|
+
}
|
|
182
|
+
.btn-delete:hover:not(:disabled) {
|
|
183
|
+
background: #f85149;
|
|
184
|
+
}
|
|
185
|
+
.toast {
|
|
186
|
+
position: fixed;
|
|
187
|
+
bottom: 20px;
|
|
188
|
+
right: 20px;
|
|
189
|
+
padding: 12px 20px;
|
|
190
|
+
border-radius: 6px;
|
|
191
|
+
font-size: 13px;
|
|
192
|
+
color: #fff;
|
|
193
|
+
opacity: 0;
|
|
194
|
+
transform: translateY(10px);
|
|
195
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
196
|
+
z-index: 1000;
|
|
197
|
+
}
|
|
198
|
+
.toast.show {
|
|
199
|
+
opacity: 1;
|
|
200
|
+
transform: translateY(0);
|
|
201
|
+
}
|
|
202
|
+
.toast.success { background: #238636; }
|
|
203
|
+
.toast.error { background: #da3633; }
|
|
204
|
+
.sidebar-content {
|
|
205
|
+
flex: 1;
|
|
206
|
+
overflow-y: auto;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Stash List View */
|
|
210
|
+
.stash-item {
|
|
211
|
+
padding: 12px 16px;
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
border-bottom: 1px solid #2d2d2d;
|
|
214
|
+
transition: background 0.1s;
|
|
215
|
+
}
|
|
216
|
+
.stash-item:hover { background: #2a2d2e; }
|
|
217
|
+
.stash-index {
|
|
218
|
+
color: #4ec9b0;
|
|
219
|
+
font-size: 12px;
|
|
220
|
+
font-weight: 600;
|
|
221
|
+
}
|
|
222
|
+
.stash-message {
|
|
223
|
+
font-size: 13px;
|
|
224
|
+
margin-top: 4px;
|
|
225
|
+
white-space: nowrap;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
text-overflow: ellipsis;
|
|
228
|
+
}
|
|
229
|
+
.stash-meta {
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
color: #808080;
|
|
232
|
+
margin-top: 4px;
|
|
233
|
+
display: flex;
|
|
234
|
+
align-items: center;
|
|
235
|
+
gap: 8px;
|
|
236
|
+
}
|
|
237
|
+
.stash-files-count {
|
|
238
|
+
background: #3c3c3c;
|
|
239
|
+
padding: 2px 6px;
|
|
240
|
+
border-radius: 10px;
|
|
241
|
+
font-size: 10px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* File List View */
|
|
245
|
+
.file-item {
|
|
246
|
+
padding: 8px 16px;
|
|
247
|
+
cursor: pointer;
|
|
248
|
+
font-size: 13px;
|
|
249
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
250
|
+
border-bottom: 1px solid #2d2d2d;
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
gap: 8px;
|
|
254
|
+
transition: background 0.1s;
|
|
255
|
+
}
|
|
256
|
+
.file-item:hover { background: #2a2d2e; }
|
|
257
|
+
.file-item.active { background: #094771; }
|
|
258
|
+
.file-status {
|
|
259
|
+
width: 8px;
|
|
260
|
+
height: 8px;
|
|
261
|
+
border-radius: 50%;
|
|
262
|
+
flex-shrink: 0;
|
|
263
|
+
background: #e2c08d;
|
|
264
|
+
}
|
|
265
|
+
.file-status.new { background: #73c991; }
|
|
266
|
+
.file-status.deleted { background: #f14c4c; }
|
|
267
|
+
.file-name {
|
|
268
|
+
flex: 1;
|
|
269
|
+
white-space: nowrap;
|
|
270
|
+
overflow: hidden;
|
|
271
|
+
text-overflow: ellipsis;
|
|
272
|
+
}
|
|
273
|
+
.file-path {
|
|
274
|
+
color: #6e7681;
|
|
275
|
+
font-size: 11px;
|
|
276
|
+
}
|
|
277
|
+
.all-changes-item {
|
|
278
|
+
padding: 10px 16px;
|
|
279
|
+
cursor: pointer;
|
|
280
|
+
font-size: 13px;
|
|
281
|
+
border-bottom: 1px solid #3c3c3c;
|
|
282
|
+
background: #2d2d2d;
|
|
283
|
+
display: flex;
|
|
284
|
+
align-items: center;
|
|
285
|
+
gap: 8px;
|
|
286
|
+
transition: background 0.1s;
|
|
287
|
+
}
|
|
288
|
+
.all-changes-item:hover { background: #3c3c3c; }
|
|
289
|
+
.all-changes-item.active { background: #094771; }
|
|
290
|
+
.all-changes-item svg {
|
|
291
|
+
width: 16px;
|
|
292
|
+
height: 16px;
|
|
293
|
+
color: #58a6ff;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Main Content */
|
|
297
|
+
.main {
|
|
298
|
+
flex: 1;
|
|
299
|
+
display: flex;
|
|
300
|
+
flex-direction: column;
|
|
301
|
+
overflow: hidden;
|
|
302
|
+
}
|
|
303
|
+
.toolbar {
|
|
304
|
+
padding: 8px 16px;
|
|
305
|
+
background: #2d2d2d;
|
|
306
|
+
border-bottom: 1px solid #3c3c3c;
|
|
307
|
+
font-size: 13px;
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
gap: 12px;
|
|
311
|
+
}
|
|
312
|
+
.toolbar-file {
|
|
313
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
314
|
+
color: #dcdcaa;
|
|
315
|
+
}
|
|
316
|
+
.diff-container {
|
|
317
|
+
flex: 1;
|
|
318
|
+
display: flex;
|
|
319
|
+
overflow: hidden;
|
|
320
|
+
}
|
|
321
|
+
.diff-pane {
|
|
322
|
+
flex: 1;
|
|
323
|
+
overflow: auto;
|
|
324
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
325
|
+
font-size: 13px;
|
|
326
|
+
line-height: 20px;
|
|
327
|
+
}
|
|
328
|
+
.diff-pane.old { background: #1e1e1e; border-right: 1px solid #3c3c3c; }
|
|
329
|
+
.diff-pane.new { background: #1e1e1e; }
|
|
330
|
+
.diff-pane-header {
|
|
331
|
+
padding: 8px 16px;
|
|
332
|
+
background: #2d2d2d;
|
|
333
|
+
border-bottom: 1px solid #3c3c3c;
|
|
334
|
+
font-size: 12px;
|
|
335
|
+
color: #808080;
|
|
336
|
+
position: sticky;
|
|
337
|
+
top: 0;
|
|
338
|
+
}
|
|
339
|
+
.diff-line {
|
|
340
|
+
padding: 0 16px;
|
|
341
|
+
white-space: pre;
|
|
342
|
+
min-height: 20px;
|
|
343
|
+
}
|
|
344
|
+
.diff-line.add { background: #2ea04326; color: #3fb950; }
|
|
345
|
+
.diff-line.del { background: #f8514926; color: #f85149; }
|
|
346
|
+
.diff-line.hunk { background: #388bfd26; color: #58a6ff; }
|
|
347
|
+
.line-num {
|
|
348
|
+
display: inline-block;
|
|
349
|
+
width: 40px;
|
|
350
|
+
color: #6e7681;
|
|
351
|
+
text-align: right;
|
|
352
|
+
margin-right: 16px;
|
|
353
|
+
user-select: none;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Empty State */
|
|
357
|
+
.empty-state {
|
|
358
|
+
flex: 1;
|
|
359
|
+
display: flex;
|
|
360
|
+
flex-direction: column;
|
|
361
|
+
align-items: center;
|
|
362
|
+
justify-content: center;
|
|
363
|
+
color: #6e7681;
|
|
364
|
+
}
|
|
365
|
+
.empty-state svg {
|
|
366
|
+
width: 64px;
|
|
367
|
+
height: 64px;
|
|
368
|
+
margin-bottom: 16px;
|
|
369
|
+
opacity: 0.5;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Scrollbar */
|
|
373
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
374
|
+
::-webkit-scrollbar-track { background: #1e1e1e; }
|
|
375
|
+
::-webkit-scrollbar-thumb { background: #424242; border-radius: 5px; }
|
|
376
|
+
::-webkit-scrollbar-thumb:hover { background: #4f4f4f; }
|
|
377
|
+
|
|
378
|
+
.hidden { display: none !important; }
|
|
379
|
+
</style>
|
|
380
|
+
</head>
|
|
381
|
+
<body>
|
|
382
|
+
<div class="container">
|
|
383
|
+
<div class="sidebar">
|
|
384
|
+
<div class="sidebar-header" id="sidebarHeader">
|
|
385
|
+
<span id="headerTitle">Stashes</span>
|
|
386
|
+
</div>
|
|
387
|
+
<div class="sidebar-content" id="sidebarContent"></div>
|
|
388
|
+
<div class="sidebar-footer">
|
|
389
|
+
<div class="sidebar-footer-brand">git-ai</div>
|
|
390
|
+
<a href="https://github.com/mehmetsagir/git-ai" target="_blank">
|
|
391
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
392
|
+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
|
393
|
+
</svg>
|
|
394
|
+
Open source on GitHub
|
|
395
|
+
</a>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
<div class="main">
|
|
399
|
+
<div class="toolbar" id="toolbar" style="display:none;">
|
|
400
|
+
<span class="toolbar-file" id="toolbarFile"></span>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="diff-container" id="diffContainer">
|
|
403
|
+
<div class="empty-state" id="emptyState">
|
|
404
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
405
|
+
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
406
|
+
</svg>
|
|
407
|
+
<div>Select a stash to view changes</div>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
<div class="toast" id="toast"></div>
|
|
413
|
+
|
|
414
|
+
<script>
|
|
415
|
+
let stashes = ${stashesJson};
|
|
416
|
+
let diffs = ${JSON.stringify(diffsJson)};
|
|
417
|
+
let files = ${JSON.stringify(filesJson)};
|
|
418
|
+
let isLoading = false;
|
|
419
|
+
|
|
420
|
+
let currentView = 'list'; // 'list' or 'detail'
|
|
421
|
+
let selectedStash = null;
|
|
422
|
+
let selectedFile = null;
|
|
423
|
+
|
|
424
|
+
function renderStashList() {
|
|
425
|
+
currentView = 'list';
|
|
426
|
+
selectedStash = null;
|
|
427
|
+
selectedFile = null;
|
|
428
|
+
|
|
429
|
+
// Update header
|
|
430
|
+
document.getElementById('sidebarHeader').innerHTML = '<span id="headerTitle">Stashes</span>';
|
|
431
|
+
|
|
432
|
+
// Hide toolbar and show empty state
|
|
433
|
+
document.getElementById('toolbar').style.display = 'none';
|
|
434
|
+
document.getElementById('diffContainer').innerHTML = \`
|
|
435
|
+
<div class="empty-state">
|
|
436
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
437
|
+
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
438
|
+
</svg>
|
|
439
|
+
<div>Select a stash to view changes</div>
|
|
440
|
+
</div>
|
|
441
|
+
\`;
|
|
442
|
+
|
|
443
|
+
// Render stash list
|
|
444
|
+
const content = document.getElementById('sidebarContent');
|
|
445
|
+
if (stashes.length === 0) {
|
|
446
|
+
content.innerHTML = '<div style="padding:20px;color:#6e7681;text-align:center;">No stashes found</div>';
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
content.innerHTML = stashes.map(s => {
|
|
451
|
+
const date = new Date(s.date).toLocaleDateString();
|
|
452
|
+
const fileCount = (files[s.index] || []).length;
|
|
453
|
+
return \`
|
|
454
|
+
<div class="stash-item" onclick="openStash(\${s.index})">
|
|
455
|
+
<div class="stash-index">stash@{\${s.index}}</div>
|
|
456
|
+
<div class="stash-message">\${escapeHtml(s.message)}</div>
|
|
457
|
+
<div class="stash-meta">
|
|
458
|
+
<span>\${s.branch}</span>
|
|
459
|
+
<span>\${date}</span>
|
|
460
|
+
<span class="stash-files-count">\${fileCount} file\${fileCount !== 1 ? 's' : ''}</span>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
\`;
|
|
464
|
+
}).join('');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function openStash(index) {
|
|
468
|
+
currentView = 'detail';
|
|
469
|
+
selectedStash = index;
|
|
470
|
+
selectedFile = null;
|
|
471
|
+
|
|
472
|
+
const stash = stashes.find(s => s.index === index);
|
|
473
|
+
const stashFiles = files[index] || [];
|
|
474
|
+
|
|
475
|
+
// Update header with back button
|
|
476
|
+
document.getElementById('sidebarHeader').innerHTML = \`
|
|
477
|
+
<button class="back-btn" onclick="renderStashList()">
|
|
478
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
479
|
+
<path d="M15 18l-6-6 6-6"/>
|
|
480
|
+
</svg>
|
|
481
|
+
Back
|
|
482
|
+
</button>
|
|
483
|
+
<span class="stash-title">stash@{\${index}}</span>
|
|
484
|
+
\`;
|
|
485
|
+
|
|
486
|
+
// Render file list with action buttons
|
|
487
|
+
const content = document.getElementById('sidebarContent');
|
|
488
|
+
let html = \`
|
|
489
|
+
<div class="stash-actions">
|
|
490
|
+
<button class="btn-apply" onclick="applyStash(\${index})" \${isLoading ? 'disabled' : ''}>
|
|
491
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
492
|
+
<path d="M5 13l4 4L19 7"/>
|
|
493
|
+
</svg>
|
|
494
|
+
Apply
|
|
495
|
+
</button>
|
|
496
|
+
<button class="btn-delete" onclick="deleteStash(\${index})" \${isLoading ? 'disabled' : ''}>
|
|
497
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
498
|
+
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
499
|
+
</svg>
|
|
500
|
+
Delete
|
|
501
|
+
</button>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="all-changes-item active" onclick="showAllChanges()">
|
|
504
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
505
|
+
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
506
|
+
</svg>
|
|
507
|
+
All Changes
|
|
508
|
+
</div>
|
|
509
|
+
\`;
|
|
510
|
+
|
|
511
|
+
html += stashFiles.map(f => {
|
|
512
|
+
const isNew = diffs[index]?.includes('new file mode') && diffs[index]?.includes(f);
|
|
513
|
+
const isDel = diffs[index]?.includes('deleted file mode') && diffs[index]?.includes(f);
|
|
514
|
+
const statusCls = isDel ? 'deleted' : (isNew ? 'new' : '');
|
|
515
|
+
const fileName = f.split('/').pop();
|
|
516
|
+
const dirPath = f.includes('/') ? f.substring(0, f.lastIndexOf('/')) : '';
|
|
517
|
+
return \`
|
|
518
|
+
<div class="file-item" data-file="\${escapeHtml(f)}" onclick="selectFile('\${escapeHtml(f)}')">
|
|
519
|
+
<div class="file-status \${statusCls}"></div>
|
|
520
|
+
<div class="file-name">
|
|
521
|
+
\${escapeHtml(fileName)}
|
|
522
|
+
\${dirPath ? \`<span class="file-path">\${escapeHtml(dirPath)}</span>\` : ''}
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
\`;
|
|
526
|
+
}).join('');
|
|
527
|
+
|
|
528
|
+
content.innerHTML = html;
|
|
529
|
+
|
|
530
|
+
// Show full diff by default
|
|
531
|
+
showAllChanges();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function showAllChanges() {
|
|
535
|
+
selectedFile = null;
|
|
536
|
+
updateFileSelection();
|
|
537
|
+
|
|
538
|
+
const diff = diffs[selectedStash] || '';
|
|
539
|
+
document.getElementById('toolbar').style.display = 'flex';
|
|
540
|
+
document.getElementById('toolbarFile').textContent = 'All Changes';
|
|
541
|
+
renderSplitDiff(diff);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function selectFile(filename) {
|
|
545
|
+
selectedFile = filename;
|
|
546
|
+
updateFileSelection();
|
|
547
|
+
|
|
548
|
+
const fullDiff = diffs[selectedStash] || '';
|
|
549
|
+
document.getElementById('toolbar').style.display = 'flex';
|
|
550
|
+
document.getElementById('toolbarFile').textContent = filename;
|
|
551
|
+
|
|
552
|
+
const fileDiff = extractFileDiff(fullDiff, filename);
|
|
553
|
+
renderSplitDiff(fileDiff);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function updateFileSelection() {
|
|
557
|
+
// Update all changes item
|
|
558
|
+
document.querySelectorAll('.all-changes-item').forEach(el => {
|
|
559
|
+
el.classList.toggle('active', selectedFile === null);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Update file items
|
|
563
|
+
document.querySelectorAll('.file-item').forEach(el => {
|
|
564
|
+
el.classList.toggle('active', el.dataset.file === selectedFile);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function extractFileDiff(fullDiff, filename) {
|
|
569
|
+
const parts = fullDiff.split(/(?=diff --git )/);
|
|
570
|
+
for (const part of parts) {
|
|
571
|
+
if (part.includes(filename)) {
|
|
572
|
+
return part;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return '';
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function renderSplitDiff(diff) {
|
|
579
|
+
const container = document.getElementById('diffContainer');
|
|
580
|
+
|
|
581
|
+
if (!diff) {
|
|
582
|
+
container.innerHTML = '<div class="empty-state"><div>No changes to display</div></div>';
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const lines = diff.split('\\n');
|
|
587
|
+
let oldLines = [];
|
|
588
|
+
let newLines = [];
|
|
589
|
+
let oldNum = 0, newNum = 0;
|
|
590
|
+
|
|
591
|
+
for (const line of lines) {
|
|
592
|
+
if (line.startsWith('@@')) {
|
|
593
|
+
const match = line.match(/@@ -(\\d+)/);
|
|
594
|
+
if (match) oldNum = parseInt(match[1]) - 1;
|
|
595
|
+
const match2 = line.match(/\\+(\\d+)/);
|
|
596
|
+
if (match2) newNum = parseInt(match2[1]) - 1;
|
|
597
|
+
oldLines.push({ type: 'hunk', text: line, num: '' });
|
|
598
|
+
newLines.push({ type: 'hunk', text: line, num: '' });
|
|
599
|
+
} else if (line.startsWith('diff --git') || line.startsWith('index ') ||
|
|
600
|
+
line.startsWith('---') || line.startsWith('+++') ||
|
|
601
|
+
line.startsWith('new file') || line.startsWith('deleted file')) {
|
|
602
|
+
// Skip header lines
|
|
603
|
+
} else if (line.startsWith('-')) {
|
|
604
|
+
oldNum++;
|
|
605
|
+
oldLines.push({ type: 'del', text: line.substring(1), num: oldNum });
|
|
606
|
+
newLines.push({ type: 'empty', text: '', num: '' });
|
|
607
|
+
} else if (line.startsWith('+')) {
|
|
608
|
+
newNum++;
|
|
609
|
+
oldLines.push({ type: 'empty', text: '', num: '' });
|
|
610
|
+
newLines.push({ type: 'add', text: line.substring(1), num: newNum });
|
|
611
|
+
} else if (line.startsWith(' ') || line === '') {
|
|
612
|
+
oldNum++;
|
|
613
|
+
newNum++;
|
|
614
|
+
oldLines.push({ type: 'context', text: line.substring(1) || '', num: oldNum });
|
|
615
|
+
newLines.push({ type: 'context', text: line.substring(1) || '', num: newNum });
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
container.innerHTML = \`
|
|
620
|
+
<div class="diff-pane old">
|
|
621
|
+
<div class="diff-pane-header">Original</div>
|
|
622
|
+
\${oldLines.map(l => \`<div class="diff-line \${l.type}"><span class="line-num">\${l.num}</span>\${escapeHtml(l.text)}</div>\`).join('')}
|
|
623
|
+
</div>
|
|
624
|
+
<div class="diff-pane new">
|
|
625
|
+
<div class="diff-pane-header">Modified</div>
|
|
626
|
+
\${newLines.map(l => \`<div class="diff-line \${l.type}"><span class="line-num">\${l.num}</span>\${escapeHtml(l.text)}</div>\`).join('')}
|
|
627
|
+
</div>
|
|
628
|
+
\`;
|
|
629
|
+
|
|
630
|
+
// Sync scroll
|
|
631
|
+
const oldPane = container.querySelector('.diff-pane.old');
|
|
632
|
+
const newPane = container.querySelector('.diff-pane.new');
|
|
633
|
+
oldPane.addEventListener('scroll', () => { newPane.scrollTop = oldPane.scrollTop; });
|
|
634
|
+
newPane.addEventListener('scroll', () => { oldPane.scrollTop = newPane.scrollTop; });
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function escapeHtml(text) {
|
|
638
|
+
return text?.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') || '';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function showToast(message, type = 'success') {
|
|
642
|
+
const toast = document.getElementById('toast');
|
|
643
|
+
toast.textContent = message;
|
|
644
|
+
toast.className = 'toast ' + type + ' show';
|
|
645
|
+
setTimeout(() => { toast.classList.remove('show'); }, 3000);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function refreshData() {
|
|
649
|
+
const res = await fetch('/api/data');
|
|
650
|
+
const data = await res.json();
|
|
651
|
+
stashes = data.stashes;
|
|
652
|
+
diffs = data.diffs;
|
|
653
|
+
files = data.files;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function applyStash(index) {
|
|
657
|
+
if (isLoading) return;
|
|
658
|
+
isLoading = true;
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
const res = await fetch('/api/apply/' + index, { method: 'POST' });
|
|
662
|
+
const data = await res.json();
|
|
663
|
+
|
|
664
|
+
if (data.success) {
|
|
665
|
+
showToast('Stash applied successfully!', 'success');
|
|
666
|
+
await refreshData();
|
|
667
|
+
renderStashList();
|
|
668
|
+
} else {
|
|
669
|
+
showToast('Failed to apply stash: ' + (data.error || 'Unknown error'), 'error');
|
|
670
|
+
}
|
|
671
|
+
} catch (err) {
|
|
672
|
+
showToast('Failed to apply stash', 'error');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
isLoading = false;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function deleteStash(index) {
|
|
679
|
+
if (isLoading) return;
|
|
680
|
+
if (!confirm('Are you sure you want to delete this stash? This cannot be undone.')) return;
|
|
681
|
+
|
|
682
|
+
isLoading = true;
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
const res = await fetch('/api/drop/' + index, { method: 'POST' });
|
|
686
|
+
const data = await res.json();
|
|
687
|
+
|
|
688
|
+
if (data.success) {
|
|
689
|
+
showToast('Stash deleted successfully!', 'success');
|
|
690
|
+
await refreshData();
|
|
691
|
+
renderStashList();
|
|
692
|
+
} else {
|
|
693
|
+
showToast('Failed to delete stash: ' + (data.error || 'Unknown error'), 'error');
|
|
694
|
+
}
|
|
695
|
+
} catch (err) {
|
|
696
|
+
showToast('Failed to delete stash', 'error');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
isLoading = false;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Initial render
|
|
703
|
+
renderStashList();
|
|
704
|
+
</script>
|
|
705
|
+
</body>
|
|
706
|
+
</html>`;
|
|
707
|
+
}
|
|
708
|
+
function openBrowser(url) {
|
|
709
|
+
const cmd = process.platform === "darwin"
|
|
710
|
+
? `open "${url}"`
|
|
711
|
+
: process.platform === "win32"
|
|
712
|
+
? `start "${url}"`
|
|
713
|
+
: `xdg-open "${url}"`;
|
|
714
|
+
(0, child_process_1.exec)(cmd, (err) => {
|
|
715
|
+
if (err) {
|
|
716
|
+
console.log(chalk_1.default.yellow(`Open manually: ${url}`));
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
async function runStashViewer() {
|
|
721
|
+
console.log(chalk_1.default.blue.bold("\nš¦ Git Stash Viewer\n"));
|
|
722
|
+
if (!(await git.isGitRepository())) {
|
|
723
|
+
console.log(chalk_1.default.red("ā Not a git repository\n"));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
console.log(chalk_1.default.gray("Loading stashes..."));
|
|
727
|
+
const stashes = await git.getStashList();
|
|
728
|
+
const diffs = new Map();
|
|
729
|
+
const files = new Map();
|
|
730
|
+
for (const stash of stashes) {
|
|
731
|
+
const diff = await git.getStashDiff(stash.index);
|
|
732
|
+
const fileList = await git.getStashFiles(stash.index);
|
|
733
|
+
diffs.set(stash.index, diff);
|
|
734
|
+
files.set(stash.index, fileList);
|
|
735
|
+
}
|
|
736
|
+
console.log(chalk_1.default.green(`ā Found ${stashes.length} stash(es)`));
|
|
737
|
+
let data = { stashes, diffs, files };
|
|
738
|
+
async function reloadStashes() {
|
|
739
|
+
const newStashes = await git.getStashList();
|
|
740
|
+
const newDiffs = new Map();
|
|
741
|
+
const newFiles = new Map();
|
|
742
|
+
for (const stash of newStashes) {
|
|
743
|
+
const diff = await git.getStashDiff(stash.index);
|
|
744
|
+
const fileList = await git.getStashFiles(stash.index);
|
|
745
|
+
newDiffs.set(stash.index, diff);
|
|
746
|
+
newFiles.set(stash.index, fileList);
|
|
747
|
+
}
|
|
748
|
+
data = { stashes: newStashes, diffs: newDiffs, files: newFiles };
|
|
749
|
+
}
|
|
750
|
+
const server = http.createServer(async (req, res) => {
|
|
751
|
+
const url = req.url || "/";
|
|
752
|
+
// API: Apply stash
|
|
753
|
+
if (url.startsWith("/api/apply/") && req.method === "POST") {
|
|
754
|
+
const index = parseInt(url.split("/").pop() || "");
|
|
755
|
+
try {
|
|
756
|
+
await git.applyStash(index);
|
|
757
|
+
await reloadStashes();
|
|
758
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
759
|
+
res.end(JSON.stringify({ success: true }));
|
|
760
|
+
}
|
|
761
|
+
catch (err) {
|
|
762
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
763
|
+
res.end(JSON.stringify({ success: false, error: String(err) }));
|
|
764
|
+
}
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
// API: Drop stash
|
|
768
|
+
if (url.startsWith("/api/drop/") && req.method === "POST") {
|
|
769
|
+
const index = parseInt(url.split("/").pop() || "");
|
|
770
|
+
try {
|
|
771
|
+
await git.dropStash(index);
|
|
772
|
+
await reloadStashes();
|
|
773
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
774
|
+
res.end(JSON.stringify({ success: true }));
|
|
775
|
+
}
|
|
776
|
+
catch (err) {
|
|
777
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
778
|
+
res.end(JSON.stringify({ success: false, error: String(err) }));
|
|
779
|
+
}
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
// API: Get fresh data
|
|
783
|
+
if (url === "/api/data" && req.method === "GET") {
|
|
784
|
+
const diffsJson = {};
|
|
785
|
+
const filesJson = {};
|
|
786
|
+
data.diffs.forEach((v, k) => { diffsJson[k] = v; });
|
|
787
|
+
data.files.forEach((v, k) => { filesJson[k] = v; });
|
|
788
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
789
|
+
res.end(JSON.stringify({ stashes: data.stashes, diffs: diffsJson, files: filesJson }));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
// Default: serve HTML
|
|
793
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
794
|
+
res.end(getHtml(data));
|
|
795
|
+
});
|
|
796
|
+
server.on("error", (err) => {
|
|
797
|
+
if (err.code === "EADDRINUSE") {
|
|
798
|
+
console.log(chalk_1.default.red(`\nā Port ${PORT} is already in use.`));
|
|
799
|
+
console.log(chalk_1.default.gray(`Run: lsof -ti:${PORT} | xargs kill -9\n`));
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
console.log(chalk_1.default.red(`\nā Server error: ${err.message}\n`));
|
|
803
|
+
}
|
|
804
|
+
process.exit(1);
|
|
805
|
+
});
|
|
806
|
+
server.listen(PORT, () => {
|
|
807
|
+
const url = `http://localhost:${PORT}`;
|
|
808
|
+
console.log(chalk_1.default.blue(`\nš Server running at ${url}`));
|
|
809
|
+
console.log(chalk_1.default.gray("Press Ctrl+C to stop\n"));
|
|
810
|
+
openBrowser(url);
|
|
811
|
+
});
|
|
812
|
+
process.on("SIGINT", () => {
|
|
813
|
+
console.log(chalk_1.default.yellow("\n\nš Server stopped\n"));
|
|
814
|
+
server.close();
|
|
815
|
+
process.exit(0);
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
//# sourceMappingURL=stash.js.map
|