gh-here 2.0.0 → 3.0.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/.claude/settings.local.json +20 -11
- package/README.md +30 -101
- package/SAMPLE.md +287 -0
- package/lib/constants.js +38 -0
- package/lib/error-handler.js +55 -0
- package/lib/file-tree-builder.js +81 -0
- package/lib/file-utils.js +43 -12
- package/lib/renderers.js +440 -194
- package/lib/server.js +120 -32
- package/lib/validation.js +77 -0
- package/package.json +1 -1
- package/public/app.js +199 -1825
- package/public/app.js.backup +1902 -0
- package/public/js/clipboard-utils.js +45 -0
- package/public/js/constants.js +60 -0
- package/public/js/draft-manager.js +36 -0
- package/public/js/editor-manager.js +159 -0
- package/public/js/file-tree.js +321 -0
- package/public/js/keyboard-handler.js +41 -0
- package/public/js/modal-manager.js +70 -0
- package/public/js/navigation.js +254 -0
- package/public/js/notification.js +23 -0
- package/public/js/search-handler.js +238 -0
- package/public/js/theme-manager.js +108 -0
- package/public/js/utils.js +123 -0
- package/public/styles.css +874 -570
- package/.channels_cache_v2.json +0 -10882
- package/.users_cache.json +0 -16187
- package/blog-post.md +0 -100
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(
|
|
5
|
-
"Bash(
|
|
6
|
-
"Bash(
|
|
7
|
-
"Bash(
|
|
8
|
-
"Bash(
|
|
9
|
-
"Bash(
|
|
10
|
-
"Bash(
|
|
11
|
-
"Bash(
|
|
12
|
-
"Bash(git checkout:*)",
|
|
4
|
+
"Bash(chmod:*)",
|
|
5
|
+
"Bash(npm install)",
|
|
6
|
+
"Bash(npm start)",
|
|
7
|
+
"Bash(pkill:*)",
|
|
8
|
+
"Bash(node:*)",
|
|
9
|
+
"Bash(curl:*)",
|
|
10
|
+
"Bash(npm install:*)",
|
|
11
|
+
"Bash(git init:*)",
|
|
13
12
|
"Bash(git add:*)",
|
|
14
13
|
"Bash(git commit:*)",
|
|
14
|
+
"Bash(git rm:*)",
|
|
15
|
+
"Bash(ssh:*)",
|
|
16
|
+
"Bash(git restore:*)",
|
|
17
|
+
"Bash(npx gh-here:*)",
|
|
18
|
+
"Bash(git checkout:*)",
|
|
15
19
|
"Bash(git push:*)",
|
|
16
|
-
"Bash(
|
|
20
|
+
"Bash(git pull:*)",
|
|
21
|
+
"Bash(npm publish:*)",
|
|
22
|
+
"Bash(timeout:*)",
|
|
23
|
+
"Bash(npm version:*)",
|
|
24
|
+
"Bash(gh release create:*)",
|
|
25
|
+
"Bash(tree:*)"
|
|
17
26
|
],
|
|
18
27
|
"deny": [],
|
|
19
28
|
"ask": []
|
|
20
29
|
}
|
|
21
|
-
}
|
|
30
|
+
}
|
package/README.md
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
# gh-here
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Why?
|
|
6
|
-
|
|
7
|
-
TUIs (Terminal User Interfaces) like Claude Code, Google Gemini CLI, and Cursor have become very popular tools for working on codebases, but they don't provide a visual view into the directories and files themselves. gh-here exists to fill that gap, so you can easily explore your project files in a familiar GitHub-esque browser GUI.
|
|
8
|
-
|
|
9
|
-
<!-- Test change for commit interface -->
|
|
3
|
+
Local GitHub-style file browser for viewing codebases. Browse directories, view files with syntax highlighting, and explore git diffs.
|
|
10
4
|
|
|
11
5
|
## Installation
|
|
12
6
|
|
|
13
|
-
Run gh-here directly with npx (no installation required):
|
|
14
|
-
|
|
15
7
|
```bash
|
|
16
8
|
npx gh-here
|
|
17
9
|
```
|
|
18
10
|
|
|
19
|
-
Or install
|
|
11
|
+
Or install globally:
|
|
20
12
|
|
|
21
13
|
```bash
|
|
22
14
|
npm install -g gh-here
|
|
@@ -24,102 +16,41 @@ npm install -g gh-here
|
|
|
24
16
|
|
|
25
17
|
## Usage
|
|
26
18
|
|
|
27
|
-
Navigate to any directory and run:
|
|
28
|
-
|
|
29
19
|
```bash
|
|
30
20
|
gh-here # Start server on available port
|
|
31
|
-
gh-here --open # Start
|
|
32
|
-
gh-here --port=8080 #
|
|
33
|
-
gh-here --open --browser=safari #
|
|
34
|
-
gh-here --open --browser=arc # Start server and open in Arc
|
|
21
|
+
gh-here --open # Start and open browser
|
|
22
|
+
gh-here --port=8080 # Use specific port
|
|
23
|
+
gh-here --open --browser=safari # Open in Safari
|
|
35
24
|
```
|
|
36
25
|
|
|
37
|
-
The app will automatically find an available port starting from 5555 and serve your current directory with a GitHub-like interface.
|
|
38
|
-
|
|
39
26
|
## Features
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
- README preview with
|
|
44
|
-
- Language statistics
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- Draft management with persistence across sessions
|
|
54
|
-
- Raw and rendered markdown views
|
|
55
|
-
- Shareable URLs with line selections (`#L10-L20`)
|
|
56
|
-
- Monaco Editor features: IntelliSense, bracket matching, folding
|
|
57
|
-
|
|
58
|
-
### 🔀 Git Integration
|
|
59
|
-
- Automatic git repository detection
|
|
60
|
-
- Clean git status indicators with colored dots in dedicated column
|
|
61
|
-
- Smart status detection for files within untracked directories
|
|
62
|
-
- Professional inline diff viewer with syntax highlighting
|
|
63
|
-
- View/Diff/Edit mode toggle for files with changes
|
|
64
|
-
- Beautiful raw git diff display with color coding
|
|
65
|
-
- Git branch display in navigation header
|
|
66
|
-
- Visual status indicators: modified (dot), untracked (purple dot), added, deleted
|
|
67
|
-
|
|
68
|
-
### ⌨️ Keyboard Navigation
|
|
69
|
-
- `j`/`k` or arrow keys to navigate files
|
|
70
|
-
- `Enter` or `o` to open files/folders
|
|
71
|
-
- `e` to edit focused file
|
|
72
|
-
- `c` to create new file
|
|
73
|
-
- `/` or `s` to focus search
|
|
74
|
-
- `h` to go up directory
|
|
75
|
-
- `t` to toggle theme
|
|
76
|
-
- `i` to toggle .gitignore filter
|
|
77
|
-
- `d` to show diff for focused file (if git changes)
|
|
78
|
-
- `r` to refresh
|
|
79
|
-
- `?` to show keyboard shortcuts
|
|
80
|
-
- `Ctrl/Cmd + S` to save file (in editor)
|
|
81
|
-
- `Esc` to close editor/dialogs
|
|
82
|
-
|
|
83
|
-
### 🌙 Themes & UI
|
|
84
|
-
- GitHub dark and light themes
|
|
85
|
-
- Smart header path (shows "gh-here" at root, path when browsing)
|
|
86
|
-
- Search functionality with keyboard shortcuts
|
|
87
|
-
- Breadcrumb navigation
|
|
88
|
-
- Error handling and loading states
|
|
89
|
-
- Notification system for user feedback
|
|
90
|
-
|
|
91
|
-
## Language Support
|
|
92
|
-
|
|
93
|
-
Supports syntax highlighting for 30+ languages including JavaScript, TypeScript, Python, Go, Rust, Java, C/C++, and many more through Monaco Editor integration.
|
|
28
|
+
- Directory browsing with file icons
|
|
29
|
+
- Syntax highlighting for 30+ languages
|
|
30
|
+
- README preview with markdown rendering
|
|
31
|
+
- Language statistics
|
|
32
|
+
- Git status indicators
|
|
33
|
+
- Diff viewer with line numbers
|
|
34
|
+
- Search files
|
|
35
|
+
- Copy/download files
|
|
36
|
+
- Rename/delete files and folders
|
|
37
|
+
- .gitignore filtering
|
|
38
|
+
- Dark/light themes
|
|
39
|
+
- Keyboard navigation
|
|
94
40
|
|
|
95
41
|
## Keyboard Shortcuts
|
|
96
42
|
|
|
97
|
-
|
|
|
98
|
-
|
|
99
|
-
| `j`
|
|
100
|
-
| `
|
|
101
|
-
| `Enter` / `o` | Open file/folder |
|
|
102
|
-
| `e` | Edit focused file |
|
|
103
|
-
| `c` | Create new file |
|
|
43
|
+
| Key | Action |
|
|
44
|
+
|-----|--------|
|
|
45
|
+
| `j`/`k` or arrows | Navigate files |
|
|
46
|
+
| `Enter` or `o` | Open file/folder |
|
|
104
47
|
| `h` | Go up directory |
|
|
105
|
-
| `/`
|
|
106
|
-
| `Ctrl/Cmd + K` | Focus search |
|
|
107
|
-
| `Ctrl/Cmd + G` | Go to top |
|
|
108
|
-
| `Shift + G` | Go to bottom |
|
|
48
|
+
| `/` or `s` | Focus search |
|
|
109
49
|
| `t` | Toggle theme |
|
|
110
|
-
| `i` | Toggle .gitignore
|
|
111
|
-
| `d` | Show diff
|
|
112
|
-
| `r` | Refresh
|
|
113
|
-
| `?` | Show
|
|
114
|
-
| `Ctrl/Cmd + S` | Save file (in editor) |
|
|
115
|
-
| `Esc` | Close editor/dialogs |
|
|
116
|
-
|
|
117
|
-
## Line Selection (Code Files)
|
|
118
|
-
|
|
119
|
-
- **Click line number**: Select single line
|
|
120
|
-
- **Shift + Click**: Select range
|
|
121
|
-
- **Ctrl/Cmd + Click**: Multi-select lines
|
|
122
|
-
- **URLs**: Automatically updated (`#L5`, `#L10-L20`, `#L1,L5-L10`)
|
|
50
|
+
| `i` | Toggle .gitignore |
|
|
51
|
+
| `d` | Show diff (if git changes) |
|
|
52
|
+
| `r` | Refresh |
|
|
53
|
+
| `?` | Show shortcuts |
|
|
123
54
|
|
|
124
55
|
## Development
|
|
125
56
|
|
|
@@ -128,11 +59,9 @@ npm install
|
|
|
128
59
|
npm start
|
|
129
60
|
```
|
|
130
61
|
|
|
131
|
-
Navigate to `http://localhost:5555` to view the interface.
|
|
132
|
-
|
|
133
62
|
## Dependencies
|
|
134
63
|
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
64
|
+
- express
|
|
65
|
+
- highlight.js
|
|
66
|
+
- marked
|
|
67
|
+
- @primer/octicons
|
package/SAMPLE.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Syntax Highlighting Test
|
|
2
|
+
|
|
3
|
+
This file contains code samples in various languages to test syntax highlighting in markdown rendering.
|
|
4
|
+
|
|
5
|
+
## JavaScript
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
function greet(name) {
|
|
9
|
+
const message = `Hello, ${name}!`;
|
|
10
|
+
console.log(message);
|
|
11
|
+
return message;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class Person {
|
|
15
|
+
constructor(name, age) {
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.age = age;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
introduce() {
|
|
21
|
+
return `My name is ${this.name} and I'm ${this.age} years old.`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Python
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
def calculate_fibonacci(n):
|
|
30
|
+
"""Calculate the nth Fibonacci number."""
|
|
31
|
+
if n <= 1:
|
|
32
|
+
return n
|
|
33
|
+
return calculate_fibonacci(n - 1) + calculate_fibonacci(n - 2)
|
|
34
|
+
|
|
35
|
+
class Animal:
|
|
36
|
+
def __init__(self, name, species):
|
|
37
|
+
self.name = name
|
|
38
|
+
self.species = species
|
|
39
|
+
|
|
40
|
+
def make_sound(self):
|
|
41
|
+
return f"{self.name} the {self.species} makes a sound"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Go
|
|
45
|
+
|
|
46
|
+
```go
|
|
47
|
+
package main
|
|
48
|
+
|
|
49
|
+
import (
|
|
50
|
+
"fmt"
|
|
51
|
+
"time"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
type User struct {
|
|
55
|
+
Name string
|
|
56
|
+
Email string
|
|
57
|
+
CreatedAt time.Time
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func (u *User) SendEmail(message string) error {
|
|
61
|
+
fmt.Printf("Sending email to %s: %s\n", u.Email, message)
|
|
62
|
+
return nil
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func main() {
|
|
66
|
+
user := &User{
|
|
67
|
+
Name: "John Doe",
|
|
68
|
+
Email: "john@example.com",
|
|
69
|
+
CreatedAt: time.Now(),
|
|
70
|
+
}
|
|
71
|
+
user.SendEmail("Welcome!")
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Rust
|
|
76
|
+
|
|
77
|
+
```rust
|
|
78
|
+
use std::collections::HashMap;
|
|
79
|
+
|
|
80
|
+
fn main() {
|
|
81
|
+
let mut scores = HashMap::new();
|
|
82
|
+
scores.insert(String::from("Blue"), 10);
|
|
83
|
+
scores.insert(String::from("Red"), 50);
|
|
84
|
+
|
|
85
|
+
for (key, value) in &scores {
|
|
86
|
+
println!("{}: {}", key, value);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
struct Rectangle {
|
|
91
|
+
width: u32,
|
|
92
|
+
height: u32,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
impl Rectangle {
|
|
96
|
+
fn area(&self) -> u32 {
|
|
97
|
+
self.width * self.height
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## TypeScript
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
interface User {
|
|
106
|
+
id: number;
|
|
107
|
+
name: string;
|
|
108
|
+
email: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function fetchUser(id: number): Promise<User> {
|
|
112
|
+
const response = await fetch(`/api/users/${id}`);
|
|
113
|
+
const data: User = await response.json();
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class UserService {
|
|
118
|
+
private users: Map<number, User> = new Map();
|
|
119
|
+
|
|
120
|
+
addUser(user: User): void {
|
|
121
|
+
this.users.set(user.id, user);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
getUser(id: number): User | undefined {
|
|
125
|
+
return this.users.get(id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Bash
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
#!/bin/bash
|
|
134
|
+
|
|
135
|
+
# Function to backup files
|
|
136
|
+
backup_files() {
|
|
137
|
+
local source_dir=$1
|
|
138
|
+
local backup_dir=$2
|
|
139
|
+
|
|
140
|
+
if [ ! -d "$backup_dir" ]; then
|
|
141
|
+
mkdir -p "$backup_dir"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
tar -czf "$backup_dir/backup-$(date +%Y%m%d).tar.gz" "$source_dir"
|
|
145
|
+
echo "Backup completed successfully!"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
backup_files "/home/user/documents" "/home/user/backups"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## SQL
|
|
152
|
+
|
|
153
|
+
```sql
|
|
154
|
+
-- Create users table
|
|
155
|
+
CREATE TABLE users (
|
|
156
|
+
id SERIAL PRIMARY KEY,
|
|
157
|
+
username VARCHAR(50) UNIQUE NOT NULL,
|
|
158
|
+
email VARCHAR(100) UNIQUE NOT NULL,
|
|
159
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
-- Insert some data
|
|
163
|
+
INSERT INTO users (username, email) VALUES
|
|
164
|
+
('john_doe', 'john@example.com'),
|
|
165
|
+
('jane_smith', 'jane@example.com');
|
|
166
|
+
|
|
167
|
+
-- Query with JOIN
|
|
168
|
+
SELECT u.username, o.order_date, o.total
|
|
169
|
+
FROM users u
|
|
170
|
+
INNER JOIN orders o ON u.id = o.user_id
|
|
171
|
+
WHERE o.total > 100
|
|
172
|
+
ORDER BY o.order_date DESC;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## JSON
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"name": "gh-here",
|
|
180
|
+
"version": "2.0.0",
|
|
181
|
+
"description": "A local GitHub-like file browser",
|
|
182
|
+
"dependencies": {
|
|
183
|
+
"express": "^4.18.2",
|
|
184
|
+
"highlight.js": "^11.9.0",
|
|
185
|
+
"marked": "^12.0.0"
|
|
186
|
+
},
|
|
187
|
+
"scripts": {
|
|
188
|
+
"start": "node bin/gh-here.js",
|
|
189
|
+
"test": "jest"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## CSS
|
|
195
|
+
|
|
196
|
+
```css
|
|
197
|
+
:root {
|
|
198
|
+
--primary-color: #0366d6;
|
|
199
|
+
--background-color: #ffffff;
|
|
200
|
+
--text-color: #24292e;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.container {
|
|
204
|
+
max-width: 1200px;
|
|
205
|
+
margin: 0 auto;
|
|
206
|
+
padding: 20px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.button {
|
|
210
|
+
display: inline-block;
|
|
211
|
+
padding: 10px 20px;
|
|
212
|
+
background-color: var(--primary-color);
|
|
213
|
+
color: white;
|
|
214
|
+
border-radius: 5px;
|
|
215
|
+
transition: background-color 0.3s ease;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.button:hover {
|
|
219
|
+
background-color: #0256c7;
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## HTML
|
|
225
|
+
|
|
226
|
+
```html
|
|
227
|
+
<!DOCTYPE html>
|
|
228
|
+
<html lang="en">
|
|
229
|
+
<head>
|
|
230
|
+
<meta charset="UTF-8">
|
|
231
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
232
|
+
<title>Sample Page</title>
|
|
233
|
+
<link rel="stylesheet" href="styles.css">
|
|
234
|
+
</head>
|
|
235
|
+
<body>
|
|
236
|
+
<header>
|
|
237
|
+
<h1>Welcome to My Site</h1>
|
|
238
|
+
<nav>
|
|
239
|
+
<ul>
|
|
240
|
+
<li><a href="#home">Home</a></li>
|
|
241
|
+
<li><a href="#about">About</a></li>
|
|
242
|
+
<li><a href="#contact">Contact</a></li>
|
|
243
|
+
</ul>
|
|
244
|
+
</nav>
|
|
245
|
+
</header>
|
|
246
|
+
<main>
|
|
247
|
+
<p>This is the main content area.</p>
|
|
248
|
+
</main>
|
|
249
|
+
<script src="script.js"></script>
|
|
250
|
+
</body>
|
|
251
|
+
</html>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Java
|
|
255
|
+
|
|
256
|
+
```java
|
|
257
|
+
import java.util.ArrayList;
|
|
258
|
+
import java.util.List;
|
|
259
|
+
|
|
260
|
+
public class BankAccount {
|
|
261
|
+
private String accountNumber;
|
|
262
|
+
private double balance;
|
|
263
|
+
private List<Transaction> transactions;
|
|
264
|
+
|
|
265
|
+
public BankAccount(String accountNumber, double initialBalance) {
|
|
266
|
+
this.accountNumber = accountNumber;
|
|
267
|
+
this.balance = initialBalance;
|
|
268
|
+
this.transactions = new ArrayList<>();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
public void deposit(double amount) {
|
|
272
|
+
if (amount > 0) {
|
|
273
|
+
balance += amount;
|
|
274
|
+
transactions.add(new Transaction("DEPOSIT", amount));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public boolean withdraw(double amount) {
|
|
279
|
+
if (amount > 0 && balance >= amount) {
|
|
280
|
+
balance -= amount;
|
|
281
|
+
transactions.add(new Transaction("WITHDRAWAL", amount));
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend constants and configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
HTTP_STATUS: {
|
|
7
|
+
OK: 200,
|
|
8
|
+
BAD_REQUEST: 400,
|
|
9
|
+
FORBIDDEN: 403,
|
|
10
|
+
NOT_FOUND: 404,
|
|
11
|
+
CONFLICT: 409,
|
|
12
|
+
INTERNAL_ERROR: 500
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
ERROR_MESSAGES: {
|
|
16
|
+
NOT_GIT_REPO: 'Not a git repository',
|
|
17
|
+
COMMIT_MESSAGE_REQUIRED: 'Commit message is required',
|
|
18
|
+
NO_FILES_SELECTED: 'No files selected',
|
|
19
|
+
FILE_PATH_REQUIRED: 'File path is required',
|
|
20
|
+
ACCESS_DENIED: 'Access denied',
|
|
21
|
+
FILE_NOT_FOUND: 'File not found',
|
|
22
|
+
ITEM_NOT_FOUND: 'Item not found',
|
|
23
|
+
ITEM_ALREADY_EXISTS: 'Item already exists',
|
|
24
|
+
CANNOT_EDIT_BINARY: 'Cannot edit binary files',
|
|
25
|
+
CANNOT_DOWNLOAD_DIRECTORIES: 'Cannot download directories'
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
GIT_STATUS_MAP: {
|
|
29
|
+
'A': 'added',
|
|
30
|
+
'M': 'modified',
|
|
31
|
+
'D': 'deleted',
|
|
32
|
+
'R': 'renamed',
|
|
33
|
+
'??': 'untracked',
|
|
34
|
+
'MM': 'mixed',
|
|
35
|
+
'AM': 'mixed',
|
|
36
|
+
'AD': 'mixed'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized error handling
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { HTTP_STATUS, ERROR_MESSAGES } = require('./constants');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Formats error response
|
|
9
|
+
*/
|
|
10
|
+
function formatErrorResponse(error, statusCode = HTTP_STATUS.INTERNAL_ERROR) {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
error: error.message || error,
|
|
14
|
+
statusCode
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sends error response
|
|
20
|
+
*/
|
|
21
|
+
function sendError(res, message, statusCode = HTTP_STATUS.INTERNAL_ERROR) {
|
|
22
|
+
res.status(statusCode).json({
|
|
23
|
+
success: false,
|
|
24
|
+
error: message
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Handles async route errors
|
|
30
|
+
*/
|
|
31
|
+
function asyncHandler(fn) {
|
|
32
|
+
return (req, res, next) => {
|
|
33
|
+
Promise.resolve(fn(req, res, next)).catch(error => {
|
|
34
|
+
console.error('Route error:', error);
|
|
35
|
+
sendError(res, 'Internal server error', HTTP_STATUS.INTERNAL_ERROR);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Logs error with context
|
|
42
|
+
*/
|
|
43
|
+
function logError(context, error) {
|
|
44
|
+
console.error(`[${context}] Error:`, error.message || error);
|
|
45
|
+
if (error.stack) {
|
|
46
|
+
console.error(error.stack);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
formatErrorResponse,
|
|
52
|
+
sendError,
|
|
53
|
+
asyncHandler,
|
|
54
|
+
logError
|
|
55
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds file tree structure for navigation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { isIgnoredByGitignore } = require('./gitignore');
|
|
8
|
+
|
|
9
|
+
function buildFileTree(dirPath, relativePath = '', gitignoreRules, workingDir, showGitignored = false, maxDepth = 5, currentDepth = 0) {
|
|
10
|
+
if (currentDepth >= maxDepth) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const items = fs.readdirSync(dirPath);
|
|
16
|
+
const tree = [];
|
|
17
|
+
|
|
18
|
+
for (const item of items) {
|
|
19
|
+
const fullPath = path.join(dirPath, item);
|
|
20
|
+
const itemRelativePath = relativePath ? `${relativePath}/${item}` : item;
|
|
21
|
+
|
|
22
|
+
// Always skip .git directory and node_modules
|
|
23
|
+
if (item === '.git' || item === 'node_modules') {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Skip other hidden files/folders unless showing gitignored
|
|
28
|
+
if (!showGitignored && item.startsWith('.')) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Skip gitignored items unless explicitly showing them
|
|
33
|
+
if (!showGitignored && isIgnoredByGitignore(fullPath, gitignoreRules, workingDir, false)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stats = fs.statSync(fullPath);
|
|
38
|
+
const isDirectory = stats.isDirectory();
|
|
39
|
+
|
|
40
|
+
const treeItem = {
|
|
41
|
+
name: item,
|
|
42
|
+
path: itemRelativePath,
|
|
43
|
+
isDirectory
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (isDirectory) {
|
|
47
|
+
treeItem.children = buildFileTree(
|
|
48
|
+
fullPath,
|
|
49
|
+
itemRelativePath,
|
|
50
|
+
gitignoreRules,
|
|
51
|
+
workingDir,
|
|
52
|
+
showGitignored,
|
|
53
|
+
maxDepth,
|
|
54
|
+
currentDepth + 1
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tree.push(treeItem);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Sort: directories first, then alphabetically
|
|
62
|
+
tree.sort((a, b) => {
|
|
63
|
+
if (a.isDirectory && !b.isDirectory) {
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
if (!a.isDirectory && b.isDirectory) {
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
return a.name.localeCompare(b.name);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return tree;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Error building file tree:', error);
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
buildFileTree
|
|
81
|
+
};
|