fix: resolve three critical bugs from GitHub issue tracker

Fix #684: Prevent Windows reserved filename creation
- Add sanitizeFilename() utility to detect and prefix Windows reserved names
  (NUL, CON, PRN, AUX, COM1-9, LPT1-9)
- Apply sanitization to save-image route to prevent "nul" file creation
- Add 23 comprehensive tests for filename sanitization edge cases

Fix #576: Detect actual dev server port from output
- Parse stdout/stderr for real server URLs (Vite, Next.js, generic formats)
- Update server URL when detected instead of using allocated PORT
- Emit dev-server:url-detected event for frontend updates
- Add 6 tests for URL detection patterns

Fix #193: Commit only feature-specific changes
- Change from 'git add -A' to branch-aware file staging
- Use git diff to find files changed on feature branch only
- Prevent committing unrelated changes from other features
- Maintain backward compatibility with main branch workflow

All fixes include comprehensive tests and maintain backward compatibility.
Test results: 1,968 tests passed (547 package + 1,421 server tests)
This commit is contained in:
DhanushSantosh
2026-02-05 10:42:56 +05:30
parent 63cae19aec
commit 84570842d3
7 changed files with 461 additions and 5 deletions

View File

@@ -380,6 +380,148 @@ describe('dev-server-service.ts', () => {
expect(service.listDevServers().result.servers).toHaveLength(0);
});
});
describe('URL detection from output', () => {
it('should detect Vite format URL', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
// Start server
await service.startDevServer(testDir, testDir);
// Simulate Vite output
mockProcess.stdout.emit('data', Buffer.from(' VITE v5.0.0 ready in 123 ms\n'));
mockProcess.stdout.emit('data', Buffer.from(' ➜ Local: http://localhost:5173/\n'));
// Give it a moment to process
await new Promise((resolve) => setTimeout(resolve, 50));
const serverInfo = service.getServerInfo(testDir);
expect(serverInfo?.url).toBe('http://localhost:5173/');
expect(serverInfo?.urlDetected).toBe(true);
});
it('should detect Next.js format URL', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
// Simulate Next.js output
mockProcess.stdout.emit(
'data',
Buffer.from('ready - started server on 0.0.0.0:3000, url: http://localhost:3000\n')
);
await new Promise((resolve) => setTimeout(resolve, 50));
const serverInfo = service.getServerInfo(testDir);
expect(serverInfo?.url).toBe('http://localhost:3000');
expect(serverInfo?.urlDetected).toBe(true);
});
it('should detect generic localhost URL', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
// Simulate generic output with URL
mockProcess.stdout.emit('data', Buffer.from('Server running at http://localhost:8080\n'));
await new Promise((resolve) => setTimeout(resolve, 50));
const serverInfo = service.getServerInfo(testDir);
expect(serverInfo?.url).toBe('http://localhost:8080');
expect(serverInfo?.urlDetected).toBe(true);
});
it('should keep initial URL if no URL detected in output', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
const result = await service.startDevServer(testDir, testDir);
// Simulate output without URL
mockProcess.stdout.emit('data', Buffer.from('Server starting...\n'));
mockProcess.stdout.emit('data', Buffer.from('Ready!\n'));
await new Promise((resolve) => setTimeout(resolve, 50));
const serverInfo = service.getServerInfo(testDir);
// Should keep the initial allocated URL
expect(serverInfo?.url).toBe(result.result?.url);
expect(serverInfo?.urlDetected).toBe(false);
});
it('should detect HTTPS URLs', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
// Simulate HTTPS dev server
mockProcess.stdout.emit('data', Buffer.from('Server at https://localhost:3443\n'));
await new Promise((resolve) => setTimeout(resolve, 50));
const serverInfo = service.getServerInfo(testDir);
expect(serverInfo?.url).toBe('https://localhost:3443');
expect(serverInfo?.urlDetected).toBe(true);
});
it('should only detect URL once (not update after first detection)', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
// First URL
mockProcess.stdout.emit('data', Buffer.from('Local: http://localhost:5173/\n'));
await new Promise((resolve) => setTimeout(resolve, 50));
const firstUrl = service.getServerInfo(testDir)?.url;
// Try to emit another URL
mockProcess.stdout.emit('data', Buffer.from('Network: http://192.168.1.1:5173/\n'));
await new Promise((resolve) => setTimeout(resolve, 50));
// Should keep the first detected URL
const serverInfo = service.getServerInfo(testDir);
expect(serverInfo?.url).toBe(firstUrl);
expect(serverInfo?.url).toBe('http://localhost:5173/');
});
});
});
// Helper to create a mock child process