Skip to content

Commit 25f57a2

Browse files
committed
feat: add dev preview script for visual-diff
- Add dev script with terminal and HTML preview modes - Include sample fixtures for TypeScript, SQL, and Python diffs - Support --terminal, --html, and --serve flags - Generate HTML preview with all 6 themes
1 parent 734365f commit 25f57a2

8 files changed

Lines changed: 359 additions & 9 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import List, Optional
2+
from dataclasses import dataclass
3+
4+
@dataclass
5+
class ProcessingResult:
6+
values: List[int]
7+
count: int
8+
total: int
9+
10+
def process_data(items: List[int], multiplier: int = 2) -> ProcessingResult:
11+
"""Process a list of items with configurable multiplier."""
12+
results = [item * multiplier for item in items if item > 0]
13+
return ProcessingResult(
14+
values=results,
15+
count=len(results),
16+
total=sum(results)
17+
)
18+
19+
class DataProcessor:
20+
def __init__(self, data: List[int], multiplier: int = 2):
21+
self.data = data
22+
self.multiplier = multiplier
23+
self._cache: Optional[ProcessingResult] = None
24+
25+
def run(self) -> ProcessingResult:
26+
if self._cache is None:
27+
self._cache = process_data(self.data, self.multiplier)
28+
return self._cache
29+
30+
def clear_cache(self) -> None:
31+
self._cache = None
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
def process_data(items):
2+
"""Process a list of items."""
3+
results = []
4+
for item in items:
5+
if item > 0:
6+
results.append(item * 2)
7+
return results
8+
9+
class DataProcessor:
10+
def __init__(self, data):
11+
self.data = data
12+
13+
def run(self):
14+
return process_data(self.data)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
SELECT
2+
u.id,
3+
u.name,
4+
u.email,
5+
u.created_at,
6+
COUNT(o.id) AS order_count,
7+
COALESCE(SUM(o.total), 0) AS total_spent
8+
FROM users u
9+
LEFT JOIN orders o ON o.user_id = u.id
10+
WHERE u.active = true
11+
AND u.created_at >= '2024-01-01'
12+
GROUP BY u.id, u.name, u.email, u.created_at
13+
HAVING COUNT(o.id) > 0
14+
ORDER BY total_spent DESC
15+
LIMIT 100;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
SELECT
2+
u.id,
3+
u.name,
4+
u.email
5+
FROM users u
6+
WHERE u.active = true
7+
ORDER BY u.name;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
interface User {
2+
id: number;
3+
name: string;
4+
email: string;
5+
createdAt: Date;
6+
isActive: boolean;
7+
}
8+
9+
type UserCreateInput = Pick<User, 'name' | 'email'>;
10+
11+
class UserService {
12+
private users: Map<number, User> = new Map();
13+
private nextId = 1;
14+
15+
async getUser(id: number): Promise<User | undefined> {
16+
return this.users.get(id);
17+
}
18+
19+
async getAllUsers(): Promise<User[]> {
20+
return Array.from(this.users.values());
21+
}
22+
23+
async createUser(input: UserCreateInput): Promise<User> {
24+
const user: User = {
25+
id: this.nextId++,
26+
name: input.name,
27+
email: input.email,
28+
createdAt: new Date(),
29+
isActive: true
30+
};
31+
this.users.set(user.id, user);
32+
return user;
33+
}
34+
35+
async deleteUser(id: number): Promise<boolean> {
36+
return this.users.delete(id);
37+
}
38+
}
39+
40+
export { UserService, User, UserCreateInput };
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
interface User {
2+
id: number;
3+
name: string;
4+
email: string;
5+
}
6+
7+
class UserService {
8+
private users: User[] = [];
9+
10+
async getUser(id: number): Promise<User | undefined> {
11+
return this.users.find(user => user.id === id);
12+
}
13+
14+
async createUser(name: string, email: string): Promise<User> {
15+
const user: User = {
16+
id: this.users.length + 1,
17+
name,
18+
email
19+
};
20+
this.users.push(user);
21+
return user;
22+
}
23+
}
24+
25+
export { UserService };

packages/visual-diff/dev/index.ts

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import * as fs from 'fs';
2+
import * as http from 'http';
3+
import * as path from 'path';
4+
5+
import {
6+
diffFiles,
7+
renderTerminal,
8+
renderHtmlDocument,
9+
themes
10+
} from '../src';
11+
12+
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
13+
const OUT_DIR = path.join(__dirname, '..', 'out');
14+
15+
interface Fixture {
16+
name: string;
17+
oldFile: string;
18+
newFile: string;
19+
language: string;
20+
}
21+
22+
const fixtures: Fixture[] = [
23+
{
24+
name: 'TypeScript',
25+
oldFile: 'typescript-old.ts',
26+
newFile: 'typescript-new.ts',
27+
language: 'typescript'
28+
},
29+
{
30+
name: 'SQL',
31+
oldFile: 'sql-old.sql',
32+
newFile: 'sql-new.sql',
33+
language: 'sql'
34+
},
35+
{
36+
name: 'Python',
37+
oldFile: 'python-old.py',
38+
newFile: 'python-new.py',
39+
language: 'python'
40+
}
41+
];
42+
43+
function loadFixture(fixture: Fixture) {
44+
const oldPath = path.join(FIXTURES_DIR, fixture.oldFile);
45+
const newPath = path.join(FIXTURES_DIR, fixture.newFile);
46+
const oldContent = fs.readFileSync(oldPath, 'utf-8');
47+
const newContent = fs.readFileSync(newPath, 'utf-8');
48+
return { oldContent, newContent };
49+
}
50+
51+
function printTerminalPreview() {
52+
console.log('\n' + '='.repeat(80));
53+
console.log(' VISUAL-DIFF TERMINAL PREVIEW');
54+
console.log('='.repeat(80) + '\n');
55+
56+
const themeNames = Object.keys(themes);
57+
58+
for (const fixture of fixtures) {
59+
const { oldContent, newContent } = loadFixture(fixture);
60+
const result = diffFiles(oldContent, newContent, fixture.oldFile, fixture.newFile);
61+
62+
console.log('\n' + '-'.repeat(80));
63+
console.log(` ${fixture.name} Diff`);
64+
console.log('-'.repeat(80));
65+
66+
for (const themeName of themeNames) {
67+
console.log(`\n>>> Theme: ${themeName}\n`);
68+
const output = renderTerminal(result, {
69+
theme: themeName,
70+
showLineNumbers: true,
71+
syntaxHighlight: true
72+
});
73+
console.log(output);
74+
console.log('');
75+
}
76+
}
77+
}
78+
79+
function generateHtmlPreview(): string {
80+
if (!fs.existsSync(OUT_DIR)) {
81+
fs.mkdirSync(OUT_DIR, { recursive: true });
82+
}
83+
84+
const htmlParts: string[] = [];
85+
86+
htmlParts.push(`<!DOCTYPE html>
87+
<html lang="en">
88+
<head>
89+
<meta charset="UTF-8">
90+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
91+
<title>Visual Diff Preview</title>
92+
<style>
93+
* { box-sizing: border-box; }
94+
body {
95+
margin: 0;
96+
padding: 20px;
97+
background: #0d1117;
98+
color: #c9d1d9;
99+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
100+
}
101+
.container { max-width: 1400px; margin: 0 auto; }
102+
h1 { text-align: center; margin-bottom: 40px; }
103+
h2 { margin-top: 40px; border-bottom: 1px solid #30363d; padding-bottom: 10px; }
104+
h3 { color: #8b949e; margin-top: 30px; }
105+
.theme-section { margin-bottom: 30px; }
106+
.diff-container { margin: 20px 0; }
107+
</style>
108+
</head>
109+
<body>
110+
<div class="container">
111+
<h1>@interweb/visual-diff Preview</h1>
112+
`);
113+
114+
const themeNames = Object.keys(themes);
115+
116+
for (const fixture of fixtures) {
117+
const { oldContent, newContent } = loadFixture(fixture);
118+
const result = diffFiles(oldContent, newContent, fixture.oldFile, fixture.newFile);
119+
120+
htmlParts.push(` <h2>${fixture.name} Diff</h2>\n`);
121+
122+
for (const themeName of themeNames) {
123+
htmlParts.push(` <div class="theme-section">\n`);
124+
htmlParts.push(` <h3>Theme: ${themeName}</h3>\n`);
125+
htmlParts.push(` <div class="diff-container">\n`);
126+
127+
const html = renderHtmlDocument(result, {
128+
theme: themeName,
129+
darkMode: true,
130+
syntaxHighlight: true
131+
});
132+
133+
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
134+
if (bodyMatch) {
135+
htmlParts.push(bodyMatch[1]);
136+
}
137+
138+
htmlParts.push(` </div>\n`);
139+
htmlParts.push(` </div>\n`);
140+
}
141+
}
142+
143+
htmlParts.push(` </div>
144+
</body>
145+
</html>`);
146+
147+
const fullHtml = htmlParts.join('');
148+
const outputPath = path.join(OUT_DIR, 'preview.html');
149+
fs.writeFileSync(outputPath, fullHtml);
150+
151+
return outputPath;
152+
}
153+
154+
function startServer(htmlPath: string, port: number) {
155+
const html = fs.readFileSync(htmlPath, 'utf-8');
156+
157+
const server = http.createServer((req, res) => {
158+
res.writeHead(200, { 'Content-Type': 'text/html' });
159+
res.end(html);
160+
});
161+
162+
server.listen(port, () => {
163+
console.log(`\nServer running at http://localhost:${port}`);
164+
console.log('Press Ctrl+C to stop\n');
165+
});
166+
}
167+
168+
function printUsage() {
169+
console.log(`
170+
Usage: pnpm dev [options]
171+
172+
Options:
173+
--terminal Show terminal preview with all themes
174+
--html Generate HTML preview file
175+
--serve Start HTTP server to view HTML preview
176+
--port=PORT Port for HTTP server (default: 3456)
177+
--help Show this help message
178+
179+
Examples:
180+
pnpm dev --terminal # Show terminal output
181+
pnpm dev --html # Generate out/preview.html
182+
pnpm dev --html --serve # Generate and serve HTML
183+
pnpm dev # Show terminal + generate HTML
184+
`);
185+
}
186+
187+
function main() {
188+
const args = process.argv.slice(2);
189+
190+
if (args.includes('--help')) {
191+
printUsage();
192+
return;
193+
}
194+
195+
const showTerminal = args.includes('--terminal') || args.length === 0;
196+
const generateHtml = args.includes('--html') || args.length === 0;
197+
const serve = args.includes('--serve');
198+
const portArg = args.find(a => a.startsWith('--port='));
199+
const port = portArg ? parseInt(portArg.split('=')[1], 10) : 3456;
200+
201+
if (showTerminal) {
202+
printTerminalPreview();
203+
}
204+
205+
if (generateHtml || serve) {
206+
const htmlPath = generateHtmlPreview();
207+
console.log(`\nHTML preview generated: ${htmlPath}`);
208+
209+
if (serve) {
210+
startServer(htmlPath, port);
211+
} else {
212+
console.log('Open this file in your browser to view the preview.\n');
213+
}
214+
}
215+
}
216+
217+
main();

packages/visual-diff/package.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@
1919
"bugs": {
2020
"url": "https://github.com/constructive-io/dev-utils/issues"
2121
},
22-
"scripts": {
23-
"copy": "makage assets",
24-
"clean": "makage clean",
25-
"prepublishOnly": "npm run build",
26-
"build": "makage build",
27-
"lint": "eslint . --fix",
28-
"test": "jest",
29-
"test:watch": "jest --watch"
30-
},
22+
"scripts": {
23+
"copy": "makage assets",
24+
"clean": "makage clean",
25+
"prepublishOnly": "npm run build",
26+
"build": "makage build",
27+
"dev": "node -r ts-node/register dev/index.ts",
28+
"lint": "eslint . --fix",
29+
"test": "jest",
30+
"test:watch": "jest --watch"
31+
},
3132
"dependencies": {
3233
"yanse": "workspace:*"
3334
},

0 commit comments

Comments
 (0)