gh-here 3.0.3 โ†’ 3.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.
Files changed (47) hide show
  1. package/.env +0 -0
  2. package/lib/constants.js +21 -16
  3. package/lib/content-search.js +212 -0
  4. package/lib/error-handler.js +39 -28
  5. package/lib/file-utils.js +438 -287
  6. package/lib/git.js +11 -55
  7. package/lib/gitignore.js +70 -41
  8. package/lib/renderers.js +17 -33
  9. package/lib/server.js +73 -196
  10. package/lib/symbol-parser.js +600 -0
  11. package/package.json +1 -1
  12. package/public/app.js +135 -68
  13. package/public/css/components/buttons.css +423 -0
  14. package/public/css/components/forms.css +171 -0
  15. package/public/css/components/modals.css +286 -0
  16. package/public/css/components/notifications.css +36 -0
  17. package/public/css/file-table.css +318 -0
  18. package/public/css/file-tree.css +269 -0
  19. package/public/css/file-viewer.css +1259 -0
  20. package/public/css/layout.css +372 -0
  21. package/public/css/main.css +35 -0
  22. package/public/css/reset.css +64 -0
  23. package/public/css/search.css +694 -0
  24. package/public/css/symbol-outline.css +279 -0
  25. package/public/css/variables.css +135 -0
  26. package/public/js/constants.js +50 -34
  27. package/public/js/content-search-handler.js +551 -0
  28. package/public/js/file-viewer.js +437 -0
  29. package/public/js/focus-mode.js +280 -0
  30. package/public/js/inline-search.js +659 -0
  31. package/public/js/modal-manager.js +14 -28
  32. package/public/js/symbol-outline.js +454 -0
  33. package/public/js/utils.js +152 -94
  34. package/.claude/settings.local.json +0 -30
  35. package/SAMPLE.md +0 -287
  36. package/lib/validation.js +0 -77
  37. package/public/app.js.backup +0 -1902
  38. package/public/highlight.css +0 -121
  39. package/public/js/draft-manager.js +0 -36
  40. package/public/js/editor-manager.js +0 -159
  41. package/public/styles.css +0 -2727
  42. package/test.js +0 -138
  43. package/tests/draftManager.test.js +0 -241
  44. package/tests/fileTypeDetection.test.js +0 -111
  45. package/tests/httpService.test.js +0 -268
  46. package/tests/languageDetection.test.js +0 -145
  47. package/tests/pathUtils.test.js +0 -136
@@ -1,268 +0,0 @@
1
- // Tests for HTTP Service abstraction
2
- // Run with: node tests/httpService.test.js
3
-
4
- // Simple mock implementation for jest.fn()
5
- function jest_fn() {
6
- const calls = [];
7
- const mockFn = (...args) => {
8
- calls.push(args);
9
- return mockFn._returnValue;
10
- };
11
- mockFn.mock = { calls };
12
- mockFn.mockResolvedValue = (value) => {
13
- mockFn._returnValue = Promise.resolve(value);
14
- return mockFn;
15
- };
16
- return mockFn;
17
- }
18
-
19
- // Replace jest with our simple implementation
20
- const jest = { fn: jest_fn };
21
-
22
- // Mock fetch for testing
23
- class MockResponse {
24
- constructor(data, options = {}) {
25
- this._data = data;
26
- this.ok = options.ok !== false;
27
- this.status = options.status || (this.ok ? 200 : 500);
28
- this.statusText = options.statusText || (this.ok ? 'OK' : 'Internal Server Error');
29
- }
30
-
31
- async json() {
32
- return this._data;
33
- }
34
-
35
- async text() {
36
- return typeof this._data === 'string' ? this._data : JSON.stringify(this._data);
37
- }
38
- }
39
-
40
- // Mock fetch implementation
41
- function createMockFetch(responseData, options = {}) {
42
- return jest.fn().mockResolvedValue(new MockResponse(responseData, options));
43
- }
44
-
45
- // HTTP Service abstraction - pure functions, easily testable
46
- function createHttpService(fetchFn = fetch) {
47
- return {
48
- // File operations
49
- async getFileContent(filePath) {
50
- const response = await fetchFn(`/api/file-content?path=${encodeURIComponent(filePath)}`);
51
- if (!response.ok) {
52
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
53
- }
54
- return response.text();
55
- },
56
-
57
- async saveFile(filePath, content) {
58
- const response = await fetchFn('/api/save-file', {
59
- method: 'POST',
60
- headers: { 'Content-Type': 'application/json' },
61
- body: JSON.stringify({ path: filePath, content })
62
- });
63
- return response.json();
64
- },
65
-
66
- async createFile(path, filename) {
67
- const response = await fetchFn('/api/create-file', {
68
- method: 'POST',
69
- headers: { 'Content-Type': 'application/json' },
70
- body: JSON.stringify({ path, filename })
71
- });
72
- return response.json();
73
- },
74
-
75
- async deleteFile(path) {
76
- const response = await fetchFn('/api/delete', {
77
- method: 'POST',
78
- headers: { 'Content-Type': 'application/json' },
79
- body: JSON.stringify({ path })
80
- });
81
- return response.json();
82
- },
83
-
84
- async renameFile(oldPath, newPath) {
85
- const response = await fetchFn('/api/rename', {
86
- method: 'POST',
87
- headers: { 'Content-Type': 'application/json' },
88
- body: JSON.stringify({ oldPath, newPath })
89
- });
90
- return response.json();
91
- },
92
-
93
- // Folder operations
94
- async createFolder(path, foldername) {
95
- const response = await fetchFn('/api/create-folder', {
96
- method: 'POST',
97
- headers: { 'Content-Type': 'application/json' },
98
- body: JSON.stringify({ path, foldername })
99
- });
100
- return response.json();
101
- },
102
-
103
- // Git operations
104
- async getGitStatus(currentPath) {
105
- const response = await fetchFn(`/api/git-status?currentPath=${encodeURIComponent(currentPath)}`);
106
- return response.json();
107
- },
108
-
109
- async getGitDiff(filePath) {
110
- const response = await fetchFn(`/api/git-diff?path=${encodeURIComponent(filePath)}`);
111
- return response.json();
112
- },
113
-
114
- async commitSelectedFiles(files, message) {
115
- const response = await fetchFn('/api/git-commit-selected', {
116
- method: 'POST',
117
- headers: { 'Content-Type': 'application/json' },
118
- body: JSON.stringify({ files, message })
119
- });
120
- return response.json();
121
- }
122
- };
123
- }
124
-
125
- // Simple test framework (since we don't have a real test runner)
126
- function test(name, fn) {
127
- try {
128
- fn();
129
- console.log(`โœ… ${name}`);
130
- } catch (error) {
131
- console.log(`โŒ ${name}: ${error.message}`);
132
- }
133
- }
134
-
135
- function assertEqual(actual, expected, message = '') {
136
- if (actual !== expected) {
137
- throw new Error(`Expected "${expected}" but got "${actual}". ${message}`);
138
- }
139
- }
140
-
141
- function assertDeepEqual(actual, expected, message = '') {
142
- const actualStr = JSON.stringify(actual);
143
- const expectedStr = JSON.stringify(expected);
144
- if (actualStr !== expectedStr) {
145
- throw new Error(`Expected ${expectedStr} but got ${actualStr}. ${message}`);
146
- }
147
- }
148
-
149
- // Test cases
150
- console.log('๐Ÿงช Testing HTTP Service\n');
151
-
152
- test('gets file content correctly', async () => {
153
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse('file content'));
154
- const httpService = createHttpService(mockFetch);
155
-
156
- const content = await httpService.getFileContent('src/app.js');
157
- assertEqual(content, 'file content');
158
- assertEqual(mockFetch.mock.calls[0][0], '/api/file-content?path=src%2Fapp.js');
159
- });
160
-
161
- test('saves file with correct parameters', async () => {
162
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
163
- const httpService = createHttpService(mockFetch);
164
-
165
- const result = await httpService.saveFile('src/app.js', 'new content');
166
- assertDeepEqual(result, { success: true });
167
-
168
- const [url, options] = mockFetch.mock.calls[0];
169
- assertEqual(url, '/api/save-file');
170
- assertEqual(options.method, 'POST');
171
- assertDeepEqual(JSON.parse(options.body), { path: 'src/app.js', content: 'new content' });
172
- });
173
-
174
- test('creates file with correct parameters', async () => {
175
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
176
- const httpService = createHttpService(mockFetch);
177
-
178
- const result = await httpService.createFile('src', 'newfile.js');
179
- assertDeepEqual(result, { success: true });
180
-
181
- const [url, options] = mockFetch.mock.calls[0];
182
- assertEqual(url, '/api/create-file');
183
- assertDeepEqual(JSON.parse(options.body), { path: 'src', filename: 'newfile.js' });
184
- });
185
-
186
- test('deletes file with correct parameters', async () => {
187
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
188
- const httpService = createHttpService(mockFetch);
189
-
190
- const result = await httpService.deleteFile('src/oldfile.js');
191
- assertDeepEqual(result, { success: true });
192
-
193
- const [url, options] = mockFetch.mock.calls[0];
194
- assertEqual(url, '/api/delete');
195
- assertDeepEqual(JSON.parse(options.body), { path: 'src/oldfile.js' });
196
- });
197
-
198
- test('renames file with correct parameters', async () => {
199
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
200
- const httpService = createHttpService(mockFetch);
201
-
202
- const result = await httpService.renameFile('src/old.js', 'src/new.js');
203
- assertDeepEqual(result, { success: true });
204
-
205
- const [url, options] = mockFetch.mock.calls[0];
206
- assertEqual(url, '/api/rename');
207
- assertDeepEqual(JSON.parse(options.body), { oldPath: 'src/old.js', newPath: 'src/new.js' });
208
- });
209
-
210
- test('creates folder with correct parameters', async () => {
211
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
212
- const httpService = createHttpService(mockFetch);
213
-
214
- const result = await httpService.createFolder('src', 'components');
215
- assertDeepEqual(result, { success: true });
216
-
217
- const [url, options] = mockFetch.mock.calls[0];
218
- assertEqual(url, '/api/create-folder');
219
- assertDeepEqual(JSON.parse(options.body), { path: 'src', foldername: 'components' });
220
- });
221
-
222
- test('gets git status with correct parameters', async () => {
223
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ files: [] }));
224
- const httpService = createHttpService(mockFetch);
225
-
226
- const result = await httpService.getGitStatus('src');
227
- assertDeepEqual(result, { files: [] });
228
-
229
- assertEqual(mockFetch.mock.calls[0][0], '/api/git-status?currentPath=src');
230
- });
231
-
232
- test('gets git diff with correct parameters', async () => {
233
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ diff: 'diff content' }));
234
- const httpService = createHttpService(mockFetch);
235
-
236
- const result = await httpService.getGitDiff('src/app.js');
237
- assertDeepEqual(result, { diff: 'diff content' });
238
-
239
- assertEqual(mockFetch.mock.calls[0][0], '/api/git-diff?path=src%2Fapp.js');
240
- });
241
-
242
- test('commits selected files with correct parameters', async () => {
243
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
244
- const httpService = createHttpService(mockFetch);
245
-
246
- const files = ['src/app.js', 'src/utils.js'];
247
- const message = 'Fix bugs';
248
- const result = await httpService.commitSelectedFiles(files, message);
249
- assertDeepEqual(result, { success: true });
250
-
251
- const [url, options] = mockFetch.mock.calls[0];
252
- assertEqual(url, '/api/git-commit-selected');
253
- assertDeepEqual(JSON.parse(options.body), { files, message });
254
- });
255
-
256
- test('handles HTTP errors correctly', async () => {
257
- const mockFetch = jest.fn().mockResolvedValue(new MockResponse('Not found', { ok: false, status: 404, statusText: 'Not Found' }));
258
- const httpService = createHttpService(mockFetch);
259
-
260
- try {
261
- await httpService.getFileContent('nonexistent.js');
262
- throw new Error('Should have thrown an error');
263
- } catch (error) {
264
- assertEqual(error.message, 'HTTP 404: Not Found');
265
- }
266
- });
267
-
268
- console.log('\n๐ŸŽ‰ HTTP Service tests complete!');
@@ -1,145 +0,0 @@
1
- // Simple unit tests for language detection
2
- // Run with: node tests/languageDetection.test.js
3
-
4
- // Import the function (in a real setup, you'd use proper modules)
5
- // For now, we'll copy the function to test it standalone
6
-
7
- function getLanguageFromExtension(filename) {
8
- const ext = filename.split('.').pop().toLowerCase();
9
- const languageMap = {
10
- // JavaScript family
11
- 'js': 'javascript',
12
- 'mjs': 'javascript',
13
- 'jsx': 'javascript',
14
- 'ts': 'typescript',
15
- 'tsx': 'typescript',
16
-
17
- // Web languages
18
- 'html': 'html',
19
- 'htm': 'html',
20
- 'css': 'css',
21
- 'scss': 'scss',
22
- 'sass': 'sass',
23
- 'less': 'less',
24
-
25
- // Data formats
26
- 'json': 'json',
27
- 'xml': 'xml',
28
- 'yaml': 'yaml',
29
- 'yml': 'yaml',
30
-
31
- // Programming languages
32
- 'py': 'python',
33
- 'java': 'java',
34
- 'go': 'go',
35
- 'rs': 'rust',
36
- 'php': 'php',
37
- 'rb': 'ruby',
38
- 'swift': 'swift',
39
- 'kt': 'kotlin',
40
- 'dart': 'dart',
41
-
42
- // Systems languages
43
- 'c': 'c',
44
- 'cpp': 'cpp',
45
- 'cc': 'cpp',
46
- 'cxx': 'cpp',
47
- 'h': 'c',
48
- 'hpp': 'cpp',
49
-
50
- // Shell and scripts
51
- 'sh': 'shell',
52
- 'bash': 'shell',
53
- 'zsh': 'shell',
54
- 'fish': 'shell',
55
- 'ps1': 'powershell',
56
-
57
- // Other languages
58
- 'sql': 'sql',
59
- 'r': 'r',
60
- 'scala': 'scala',
61
- 'clj': 'clojure',
62
- 'lua': 'lua',
63
- 'pl': 'perl',
64
- 'groovy': 'groovy',
65
-
66
- // Config and text
67
- 'md': 'markdown',
68
- 'txt': 'plaintext',
69
- 'log': 'plaintext'
70
- };
71
-
72
- // Special filename handling
73
- const basename = filename.toLowerCase();
74
- if (basename === 'dockerfile' || basename.startsWith('dockerfile.')) return 'dockerfile';
75
- if (basename === 'makefile') return 'makefile';
76
- if (basename.startsWith('.env')) return 'dotenv';
77
- if (basename === 'package.json' || basename === 'composer.json') return 'json';
78
-
79
- return languageMap[ext] || 'plaintext';
80
- }
81
-
82
- // Simple test framework
83
- function test(name, fn) {
84
- try {
85
- fn();
86
- console.log(`โœ… ${name}`);
87
- } catch (error) {
88
- console.log(`โŒ ${name}: ${error.message}`);
89
- }
90
- }
91
-
92
- function assertEqual(actual, expected, message = '') {
93
- if (actual !== expected) {
94
- throw new Error(`Expected "${expected}" but got "${actual}". ${message}`);
95
- }
96
- }
97
-
98
- // Test cases
99
- console.log('๐Ÿงช Testing Language Detection Function\n');
100
-
101
- test('detects JavaScript files', () => {
102
- assertEqual(getLanguageFromExtension('app.js'), 'javascript');
103
- assertEqual(getLanguageFromExtension('module.mjs'), 'javascript');
104
- assertEqual(getLanguageFromExtension('Component.jsx'), 'javascript');
105
- });
106
-
107
- test('detects TypeScript files', () => {
108
- assertEqual(getLanguageFromExtension('main.ts'), 'typescript');
109
- assertEqual(getLanguageFromExtension('Component.tsx'), 'typescript');
110
- });
111
-
112
- test('detects web languages', () => {
113
- assertEqual(getLanguageFromExtension('index.html'), 'html');
114
- assertEqual(getLanguageFromExtension('styles.css'), 'css');
115
- assertEqual(getLanguageFromExtension('styles.scss'), 'scss');
116
- });
117
-
118
- test('detects programming languages', () => {
119
- assertEqual(getLanguageFromExtension('script.py'), 'python');
120
- assertEqual(getLanguageFromExtension('Main.java'), 'java');
121
- assertEqual(getLanguageFromExtension('main.go'), 'go');
122
- assertEqual(getLanguageFromExtension('lib.rs'), 'rust');
123
- });
124
-
125
- test('detects special filenames', () => {
126
- assertEqual(getLanguageFromExtension('Dockerfile'), 'dockerfile');
127
- assertEqual(getLanguageFromExtension('dockerfile.prod'), 'dockerfile');
128
- assertEqual(getLanguageFromExtension('Makefile'), 'makefile');
129
- assertEqual(getLanguageFromExtension('.env'), 'dotenv');
130
- assertEqual(getLanguageFromExtension('.env.local'), 'dotenv');
131
- assertEqual(getLanguageFromExtension('package.json'), 'json');
132
- });
133
-
134
- test('defaults to plaintext for unknown extensions', () => {
135
- assertEqual(getLanguageFromExtension('file.unknown'), 'plaintext');
136
- assertEqual(getLanguageFromExtension('README'), 'plaintext');
137
- assertEqual(getLanguageFromExtension('file.xyz'), 'plaintext');
138
- });
139
-
140
- test('handles files without extensions', () => {
141
- assertEqual(getLanguageFromExtension('README'), 'plaintext');
142
- assertEqual(getLanguageFromExtension('LICENSE'), 'plaintext');
143
- });
144
-
145
- console.log('\n๐ŸŽ‰ Language detection tests complete!');
@@ -1,136 +0,0 @@
1
- // Tests for Path Utilities
2
- // Run with: node tests/pathUtils.test.js
3
-
4
- // Path utility functions extracted for testability
5
- const PathUtils = {
6
- // Extract current path from URL parameters
7
- getCurrentPath() {
8
- const currentUrl = new URL(window.location.href);
9
- return currentUrl.searchParams.get('path') || '';
10
- },
11
-
12
- // Navigate to parent directory
13
- getParentPath(currentPath) {
14
- if (!currentPath || currentPath === '') {
15
- return null; // Already at root
16
- }
17
-
18
- const pathParts = currentPath.split('/').filter(p => p);
19
- if (pathParts.length === 0) {
20
- return null; // Already at root
21
- }
22
-
23
- pathParts.pop();
24
- return pathParts.join('/');
25
- },
26
-
27
- // Build file path from directory and filename
28
- buildFilePath(currentPath, filename) {
29
- return currentPath ? `${currentPath}/${filename}` : filename;
30
- },
31
-
32
- // Get filename from full path
33
- getFileName(filePath) {
34
- return filePath.split('/').pop() || 'file.txt';
35
- },
36
-
37
- // Build URL with encoded path parameter
38
- buildPathUrl(basePath, targetPath) {
39
- return targetPath ? `${basePath}?path=${encodeURIComponent(targetPath)}` : basePath;
40
- },
41
-
42
- // Extract directory path from file path
43
- getDirectoryPath(filePath) {
44
- const parts = filePath.split('/').filter(p => p);
45
- if (parts.length <= 1) return '';
46
- return parts.slice(0, -1).join('/');
47
- }
48
- };
49
-
50
- // Simple test framework
51
- function test(name, fn) {
52
- try {
53
- fn();
54
- console.log(`โœ… ${name}`);
55
- } catch (error) {
56
- console.log(`โŒ ${name}: ${error.message}`);
57
- }
58
- }
59
-
60
- function assertEqual(actual, expected, message = '') {
61
- if (actual !== expected) {
62
- throw new Error(`Expected "${expected}" but got "${actual}". ${message}`);
63
- }
64
- }
65
-
66
- function assertNull(actual, message = '') {
67
- if (actual !== null) {
68
- throw new Error(`Expected null but got "${actual}". ${message}`);
69
- }
70
- }
71
-
72
- // Test cases
73
- console.log('๐Ÿงช Testing Path Utilities\n');
74
-
75
- test('gets parent path correctly', () => {
76
- assertEqual(PathUtils.getParentPath('src/components/App.js'), 'src/components');
77
- assertEqual(PathUtils.getParentPath('src/App.js'), 'src');
78
- assertEqual(PathUtils.getParentPath('App.js'), '');
79
- assertNull(PathUtils.getParentPath(''));
80
- assertNull(PathUtils.getParentPath(null));
81
- });
82
-
83
- test('builds file paths correctly', () => {
84
- assertEqual(PathUtils.buildFilePath('src/components', 'App.js'), 'src/components/App.js');
85
- assertEqual(PathUtils.buildFilePath('src', 'App.js'), 'src/App.js');
86
- assertEqual(PathUtils.buildFilePath('', 'App.js'), 'App.js');
87
- assertEqual(PathUtils.buildFilePath(null, 'App.js'), 'App.js');
88
- });
89
-
90
- test('gets filename from path correctly', () => {
91
- assertEqual(PathUtils.getFileName('src/components/App.js'), 'App.js');
92
- assertEqual(PathUtils.getFileName('App.js'), 'App.js');
93
- assertEqual(PathUtils.getFileName(''), 'file.txt');
94
- assertEqual(PathUtils.getFileName('src/components/'), 'file.txt');
95
- });
96
-
97
- test('builds URLs with path parameters', () => {
98
- assertEqual(PathUtils.buildPathUrl('/', 'src/components'), '/?path=src%2Fcomponents');
99
- assertEqual(PathUtils.buildPathUrl('/new', 'src'), '/new?path=src');
100
- assertEqual(PathUtils.buildPathUrl('/', ''), '/');
101
- assertEqual(PathUtils.buildPathUrl('/new', null), '/new');
102
- });
103
-
104
- test('gets directory path from file path', () => {
105
- assertEqual(PathUtils.getDirectoryPath('src/components/App.js'), 'src/components');
106
- assertEqual(PathUtils.getDirectoryPath('src/App.js'), 'src');
107
- assertEqual(PathUtils.getDirectoryPath('App.js'), '');
108
- assertEqual(PathUtils.getDirectoryPath(''), '');
109
- });
110
-
111
- test('handles git commit dialog scenarios correctly', () => {
112
- // Test cases that match the commit dialog usage pattern
113
- // where we extract directory from file.name
114
- assertEqual(PathUtils.getDirectoryPath('src/components/Button.tsx'), 'src/components');
115
- assertEqual(PathUtils.getDirectoryPath('lib/utils/helper.js'), 'lib/utils');
116
- assertEqual(PathUtils.getDirectoryPath('tests/unit/test.js'), 'tests/unit');
117
- assertEqual(PathUtils.getDirectoryPath('package.json'), ''); // Root level file
118
- assertEqual(PathUtils.getDirectoryPath('docs/README.md'), 'docs');
119
-
120
- // Edge cases from commit dialog
121
- assertEqual(PathUtils.getDirectoryPath('very/deeply/nested/folder/structure/file.txt'), 'very/deeply/nested/folder/structure');
122
- assertEqual(PathUtils.getDirectoryPath('single-level.txt'), '');
123
- });
124
-
125
- test('handles edge cases in parent path', () => {
126
- assertEqual(PathUtils.getParentPath('a/b/c/d/e'), 'a/b/c/d');
127
- assertEqual(PathUtils.getParentPath('single'), '');
128
- assertNull(PathUtils.getParentPath(''));
129
- });
130
-
131
- test('handles edge cases in file path building', () => {
132
- assertEqual(PathUtils.buildFilePath('a/b/c', 'file.txt'), 'a/b/c/file.txt');
133
- assertEqual(PathUtils.buildFilePath('', 'README.md'), 'README.md');
134
- });
135
-
136
- console.log('\n๐ŸŽ‰ Path utilities tests complete!');