gh-here 3.0.1 → 3.0.2
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 +14 -0
- package/lib/renderers.js +9 -9
- package/package.json +7 -4
- package/public/styles.css +11 -2
- package/test.js +138 -0
package/README.md
CHANGED
|
@@ -62,6 +62,20 @@ npm install
|
|
|
62
62
|
npm start
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
### Running Tests (Optional)
|
|
66
|
+
|
|
67
|
+
Tests use Playwright for smoke testing but are optional for development:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# First time only - install Chromium for testing (~210MB)
|
|
71
|
+
npx playwright install chromium
|
|
72
|
+
|
|
73
|
+
# Run tests
|
|
74
|
+
npm test
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Note**: End users don't need Playwright - it's only installed when you clone the repo and run `npm install` (devDependency).
|
|
78
|
+
|
|
65
79
|
## Dependencies
|
|
66
80
|
|
|
67
81
|
- express - Web server
|
package/lib/renderers.js
CHANGED
|
@@ -103,7 +103,7 @@ function renderDirectory(currentPath, items, showGitignored = false, gitBranch =
|
|
|
103
103
|
<html data-theme="dark">
|
|
104
104
|
<head>
|
|
105
105
|
<title>gh-here: ${currentPath || 'Root'}</title>
|
|
106
|
-
<link rel="stylesheet" href="/static/styles.css?v
|
|
106
|
+
<link rel="stylesheet" href="/static/styles.css?v=3.0.5">
|
|
107
107
|
<script>
|
|
108
108
|
// Check localStorage and add showGitignored param if needed (before page renders)
|
|
109
109
|
(function() {
|
|
@@ -306,18 +306,18 @@ function generateLanguageStats(items) {
|
|
|
306
306
|
async function renderFileDiff(filePath, ext, gitInfo, gitRepoRoot, workingDir = null, gitBranch = null) {
|
|
307
307
|
const workingDirName = workingDir ? path.basename(workingDir) : null;
|
|
308
308
|
const breadcrumbs = generateBreadcrumbs(filePath, null, workingDirName);
|
|
309
|
-
|
|
309
|
+
|
|
310
310
|
// Get git diff for the file
|
|
311
311
|
return new Promise((resolve, reject) => {
|
|
312
|
-
const diffCommand = gitInfo.staged ?
|
|
313
|
-
`git diff --cached "${filePath}"` :
|
|
312
|
+
const diffCommand = gitInfo.staged ?
|
|
313
|
+
`git diff --cached "${filePath}"` :
|
|
314
314
|
`git diff "${filePath}"`;
|
|
315
|
-
|
|
316
|
-
exec(diffCommand, { cwd: gitRepoRoot }, (error, stdout) => {
|
|
315
|
+
|
|
316
|
+
exec(diffCommand, { cwd: gitRepoRoot }, (error, stdout, stderr) => {
|
|
317
317
|
if (error) {
|
|
318
318
|
return reject(error);
|
|
319
319
|
}
|
|
320
|
-
|
|
320
|
+
|
|
321
321
|
const diffContent = renderRawDiff(stdout, ext);
|
|
322
322
|
const currentParams = new URLSearchParams({ path: filePath });
|
|
323
323
|
const viewUrl = `/?${currentParams.toString()}`;
|
|
@@ -407,7 +407,7 @@ async function renderFileDiff(filePath, ext, gitInfo, gitRepoRoot, workingDir =
|
|
|
407
407
|
<html data-theme="dark">
|
|
408
408
|
<head>
|
|
409
409
|
<title>gh-here: ${path.basename(filePath)} (diff)</title>
|
|
410
|
-
<link rel="stylesheet" href="/static/styles.css?v
|
|
410
|
+
<link rel="stylesheet" href="/static/styles.css?v=3.0.5">
|
|
411
411
|
<link rel="stylesheet" href="/static/highlight.css?v=${Date.now()}">
|
|
412
412
|
<script>
|
|
413
413
|
// Check localStorage and add showGitignored param if needed (before page renders)
|
|
@@ -787,7 +787,7 @@ async function renderFile(filePath, content, ext, viewMode = 'rendered', gitStat
|
|
|
787
787
|
<html data-theme="dark">
|
|
788
788
|
<head>
|
|
789
789
|
<title>gh-here: ${path.basename(filePath)}</title>
|
|
790
|
-
<link rel="stylesheet" href="/static/styles.css?v
|
|
790
|
+
<link rel="stylesheet" href="/static/styles.css?v=3.0.5">
|
|
791
791
|
<link rel="stylesheet" href="/static/highlight.css?v=${Date.now()}">
|
|
792
792
|
<script>
|
|
793
793
|
// Check localStorage and add showGitignored param if needed (before page renders)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gh-here",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "A local GitHub-like file browser for viewing code",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"start": "node bin/gh-here.js",
|
|
15
|
-
"test": "
|
|
15
|
+
"test": "node test.js"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
18
|
"github",
|
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
"author": "",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@primer/octicons": "^19.8.0",
|
|
26
27
|
"express": "^4.18.2",
|
|
27
28
|
"highlight.js": "^11.9.0",
|
|
28
|
-
"marked": "^12.0.0"
|
|
29
|
-
|
|
29
|
+
"marked": "^12.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"playwright": "^1.56.1"
|
|
30
33
|
}
|
|
31
34
|
}
|
package/public/styles.css
CHANGED
|
@@ -120,7 +120,16 @@ header {
|
|
|
120
120
|
font-weight: 600;
|
|
121
121
|
margin: 0;
|
|
122
122
|
color: var(--text-primary);
|
|
123
|
-
letter-spacing:
|
|
123
|
+
letter-spacing: 0.02em;
|
|
124
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.header-left h1::before {
|
|
128
|
+
content: '$';
|
|
129
|
+
color: var(--link-color);
|
|
130
|
+
margin-right: 8px;
|
|
131
|
+
opacity: 0.8;
|
|
132
|
+
font-weight: 400;
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
.header-path {
|
|
@@ -1623,7 +1632,7 @@ main {
|
|
|
1623
1632
|
|
|
1624
1633
|
.line-content {
|
|
1625
1634
|
white-space: pre;
|
|
1626
|
-
display: inline
|
|
1635
|
+
display: inline;
|
|
1627
1636
|
padding: 0 10px;
|
|
1628
1637
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
1629
1638
|
font-size: 12px;
|
package/test.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight smoke tests for gh-here
|
|
3
|
+
* Run with: node test.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { chromium } = require('playwright');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const TEST_PORT = 5556; // Use different port to avoid conflicts
|
|
10
|
+
const BASE_URL = `http://localhost:${TEST_PORT}`;
|
|
11
|
+
|
|
12
|
+
async function sleep(ms) {
|
|
13
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function runTests() {
|
|
17
|
+
console.log('Starting gh-here server...');
|
|
18
|
+
|
|
19
|
+
// Start server
|
|
20
|
+
const server = spawn('node', ['bin/gh-here.js', `--port=${TEST_PORT}`], {
|
|
21
|
+
cwd: __dirname,
|
|
22
|
+
stdio: 'pipe'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Wait for server to start
|
|
26
|
+
await sleep(2000);
|
|
27
|
+
|
|
28
|
+
let browser;
|
|
29
|
+
let failures = 0;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
console.log('Launching browser...');
|
|
33
|
+
browser = await chromium.launch({ headless: true });
|
|
34
|
+
const context = await browser.newContext();
|
|
35
|
+
const page = await context.newPage();
|
|
36
|
+
|
|
37
|
+
// Test 1: Root page loads
|
|
38
|
+
console.log('\n✓ Test 1: Root page loads');
|
|
39
|
+
await page.goto(BASE_URL);
|
|
40
|
+
const title = await page.title();
|
|
41
|
+
if (!title.includes('gh-here')) {
|
|
42
|
+
console.error(' ✗ FAILED: Page title incorrect');
|
|
43
|
+
failures++;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Test 2: File tree exists (may be hidden on root)
|
|
47
|
+
console.log('✓ Test 2: File tree element exists');
|
|
48
|
+
const fileTreeExists = await page.$('#file-tree');
|
|
49
|
+
if (!fileTreeExists) {
|
|
50
|
+
console.error(' ✗ FAILED: File tree element not found');
|
|
51
|
+
failures++;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Test 3: Navigate to a file and check line numbers display correctly
|
|
55
|
+
console.log('✓ Test 3: File view with line numbers');
|
|
56
|
+
await page.goto(`${BASE_URL}/?path=lib/renderers.js`);
|
|
57
|
+
await page.waitForSelector('.line-container', { timeout: 5000 });
|
|
58
|
+
|
|
59
|
+
// Check that line numbers are in a vertical column (not nested)
|
|
60
|
+
const lineContainers = await page.$$('.line-container');
|
|
61
|
+
if (lineContainers.length < 10) {
|
|
62
|
+
console.error(' ✗ FAILED: Not enough line containers found');
|
|
63
|
+
failures++;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check line 1 and line 2 have sequential numbers
|
|
67
|
+
const line1 = await page.$('.line-container[data-line="1"]');
|
|
68
|
+
const line2 = await page.$('.line-container[data-line="2"]');
|
|
69
|
+
if (!line1 || !line2) {
|
|
70
|
+
console.error(' ✗ FAILED: Line containers missing data-line attributes');
|
|
71
|
+
failures++;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Verify line numbers are not nested (check display property)
|
|
75
|
+
const line1Display = await page.$eval('.line-container[data-line="1"]',
|
|
76
|
+
el => window.getComputedStyle(el).display
|
|
77
|
+
);
|
|
78
|
+
if (line1Display !== 'block') {
|
|
79
|
+
console.error(` ✗ FAILED: Line containers should have display:block, got ${line1Display}`);
|
|
80
|
+
failures++;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Test 4: Check if modified files show diff button
|
|
84
|
+
console.log('✓ Test 4: Modified files show diff button');
|
|
85
|
+
await page.goto(BASE_URL);
|
|
86
|
+
const diffButtons = await page.$$('.diff-btn');
|
|
87
|
+
// Should have at least one diff button if there are modified files
|
|
88
|
+
console.log(` Found ${diffButtons.length} diff buttons`);
|
|
89
|
+
|
|
90
|
+
// Test 5: Gitignore toggle exists and is clickable
|
|
91
|
+
console.log('✓ Test 5: Gitignore toggle button');
|
|
92
|
+
const gitignoreToggle = await page.$('#gitignore-toggle');
|
|
93
|
+
if (!gitignoreToggle) {
|
|
94
|
+
console.error(' ✗ FAILED: Gitignore toggle button not found');
|
|
95
|
+
failures++;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Test 6: Theme toggle works
|
|
99
|
+
console.log('✓ Test 6: Theme toggle');
|
|
100
|
+
const themeToggle = await page.$('#theme-toggle');
|
|
101
|
+
if (!themeToggle) {
|
|
102
|
+
console.error(' ✗ FAILED: Theme toggle button not found');
|
|
103
|
+
failures++;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Test 7: Search functionality
|
|
107
|
+
console.log('✓ Test 7: Search input exists');
|
|
108
|
+
const searchInput = await page.$('#file-search, #root-file-search');
|
|
109
|
+
if (!searchInput) {
|
|
110
|
+
console.error(' ✗ FAILED: Search input not found');
|
|
111
|
+
failures++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('\n' + '='.repeat(50));
|
|
115
|
+
if (failures === 0) {
|
|
116
|
+
console.log('✓ All tests passed!');
|
|
117
|
+
} else {
|
|
118
|
+
console.log(`✗ ${failures} test(s) failed`);
|
|
119
|
+
}
|
|
120
|
+
console.log('='.repeat(50) + '\n');
|
|
121
|
+
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('\n✗ Test failed with error:', error.message);
|
|
124
|
+
failures++;
|
|
125
|
+
} finally {
|
|
126
|
+
if (browser) {
|
|
127
|
+
await browser.close();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Kill server
|
|
131
|
+
server.kill();
|
|
132
|
+
|
|
133
|
+
// Exit with appropriate code
|
|
134
|
+
process.exit(failures > 0 ? 1 : 0);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
runTests();
|