Skip to content

Commit d95fc3b

Browse files
deepu-mungamuri94dmungamuri
authored andcommitted
feat: add comprehensive error capture system for LWC local dev
@W-19978113@ This adds a robust error capture and reporting system for LWC local development that captures runtime errors from the browser and displays them formatted in the terminal. Features: - ErrorStore: In-memory store for runtime errors with deduplication - Error Capture HTTP Server: Standalone server on LWC port + 1 - Error Middleware: Express middleware for capturing/querying errors - Error Formatter: CLI-friendly formatted error output with colors - Stack Trace Utils: Parse and sanitize stack traces Components: - src/lwc-dev-server/errorStore.ts: Error storage with statistics - src/lwc-dev-server/errorHttpServer.ts: HTTP server for error endpoints - src/lwc-dev-server/errorMiddleware.ts: Express middleware - src/shared/errorFormatter.ts: Format errors for terminal display - src/shared/stackTraceUtils.ts: Stack trace parsing utilities - src/types/errorPayload.ts: TypeScript types for error diagnostics Testing: - Comprehensive unit tests for all error capture components - E2E tests for error capture workflow - Test fixtures for validation Updated: - package.json: Added express dependency - src/lwc-dev-server/index.ts: Integrated error capture server - yarn.lock: Updated dependencies Error Capture Endpoints: - POST /_dev/errors - Capture error reports - GET /_dev/errors - Query errors with filters - DELETE /_dev/errors - Clear all errors - GET /_dev/errors/stats - Get error statistics - GET /_dev/health - Health check Auto-clears errors on server restart. Can also manually clear via DELETE endpoint.
1 parent 6f2f603 commit d95fc3b

21 files changed

+4397
-174
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@salesforce/lwc-dev-mobile-core": "4.0.0-alpha.13",
1717
"@salesforce/sf-plugins-core": "^11.2.4",
1818
"axios": "^1.12.2",
19+
"express": "^5.1.0",
1920
"glob": "^10.4.5",
2021
"lwc": "~8.23.0",
2122
"node-fetch": "^3.3.2",
@@ -27,6 +28,7 @@
2728
"@salesforce/cli-plugins-testkit": "^5.3.41",
2829
"@salesforce/dev-scripts": "^11.0.4",
2930
"@salesforce/plugin-command-reference": "^3.1.74",
31+
"@types/express": "^5.0.3",
3032
"@types/node-fetch": "^2.6.13",
3133
"@types/xml2js": "^0.4.14",
3234
"@typescript-eslint/eslint-plugin": "^6.21.0",
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright 2025, Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { Server } from 'node:http';
18+
// eslint-disable-next-line import/no-extraneous-dependencies
19+
import express, { Express } from 'express';
20+
import { Logger } from '@salesforce/core';
21+
import { ErrorStore } from './errorStore.js';
22+
import { createCombinedErrorMiddleware, createErrorCORSMiddleware } from './errorMiddleware.js';
23+
24+
/**
25+
* Configuration for the standalone error capture HTTP server
26+
*/
27+
export type ErrorServerConfig = {
28+
/** Port for the error capture server */
29+
port: number;
30+
/** Error store instance */
31+
errorStore: ErrorStore;
32+
/** Logger instance */
33+
logger: Logger;
34+
/** Project root directory */
35+
projectRoot: string;
36+
/** Whether to log errors to console */
37+
logToConsole?: boolean;
38+
/** Whether to bind to localhost only (recommended for security) */
39+
localhostOnly?: boolean;
40+
};
41+
42+
/**
43+
* Standalone HTTP server for error capture endpoints.
44+
*
45+
* This server runs independently of the LWC dev server and provides
46+
* HTTP endpoints for error reporting, querying, and management.
47+
*
48+
* Endpoints:
49+
* - POST /_dev/errors - Capture error reports
50+
* - GET /_dev/errors - Query stored errors (with filters)
51+
* - DELETE /_dev/errors - Clear all errors
52+
* - GET /_dev/errors/stats - Get error statistics
53+
*
54+
* @example
55+
* ```typescript
56+
* const errorServer = await startErrorCaptureServer({
57+
* port: 8082,
58+
* errorStore: getErrorStore(),
59+
* logger,
60+
* projectRoot: '/path/to/project',
61+
* });
62+
*
63+
* // Later...
64+
* await errorServer.stop();
65+
* ```
66+
*/
67+
export class ErrorCaptureServer {
68+
private app: Express;
69+
private server: Server | null = null;
70+
private config: ErrorServerConfig;
71+
72+
public constructor(config: ErrorServerConfig) {
73+
this.config = config;
74+
this.app = express();
75+
this.setupMiddleware();
76+
}
77+
78+
/**
79+
* Start the error capture server
80+
*/
81+
public async start(): Promise<void> {
82+
return new Promise((resolve, reject) => {
83+
const { port, localhostOnly = true, logger } = this.config;
84+
85+
try {
86+
// Bind to localhost only for security (unless explicitly disabled)
87+
const host = localhostOnly ? 'localhost' : '0.0.0.0';
88+
89+
this.server = this.app.listen(port, host, () => {
90+
logger.info(`[ErrorCapture] Error capture server started at http://${host}:${port}`);
91+
logger.info('[ErrorCapture] Available endpoints:');
92+
logger.info('[ErrorCapture] POST /_dev/errors - Capture error reports');
93+
logger.info(
94+
'[ErrorCapture] GET /_dev/errors - Query errors (supports ?component=, ?severity=, ?limit=)'
95+
);
96+
logger.info('[ErrorCapture] DELETE /_dev/errors - Clear all errors');
97+
logger.info('[ErrorCapture] GET /_dev/errors/stats - Get statistics');
98+
logger.info('[ErrorCapture] GET /_dev/health - Health check');
99+
resolve();
100+
});
101+
102+
this.server.on('error', (err: NodeJS.ErrnoException) => {
103+
if (err.code === 'EADDRINUSE') {
104+
logger.error(`[ErrorCapture] Port ${port} is already in use. Please use a different port.`);
105+
reject(new Error(`Port ${port} is already in use`));
106+
} else {
107+
logger.error(`[ErrorCapture] Server error: ${err.message}`);
108+
reject(err);
109+
}
110+
});
111+
} catch (err) {
112+
logger.error(
113+
`[ErrorCapture] Failed to start error capture server: ${err instanceof Error ? err.message : String(err)}`
114+
);
115+
reject(err);
116+
}
117+
});
118+
}
119+
120+
/**
121+
* Stop the error capture server
122+
*/
123+
public async stop(): Promise<void> {
124+
return new Promise((resolve, reject) => {
125+
if (!this.server) {
126+
resolve();
127+
return;
128+
}
129+
130+
this.server.close((err) => {
131+
if (err) {
132+
this.config.logger.error(`[ErrorCapture] Error stopping server: ${err.message}`);
133+
reject(err);
134+
} else {
135+
this.config.logger.info('[ErrorCapture] Error capture server stopped');
136+
this.server = null;
137+
resolve();
138+
}
139+
});
140+
});
141+
}
142+
143+
/**
144+
* Get the underlying Express app (for testing)
145+
*/
146+
public getApp(): Express {
147+
return this.app;
148+
}
149+
150+
/**
151+
* Get the server instance (for testing)
152+
*/
153+
public getServer(): Server | null {
154+
return this.server;
155+
}
156+
157+
/**
158+
* Check if server is running
159+
*/
160+
public isRunning(): boolean {
161+
return this.server !== null && this.server.listening;
162+
}
163+
164+
/**
165+
* Set up Express middleware
166+
*/
167+
private setupMiddleware(): void {
168+
const { errorStore, logger, projectRoot, logToConsole = true } = this.config;
169+
170+
// Parse JSON request bodies
171+
this.app.use(express.json({ limit: '10mb' }));
172+
173+
// Enable CORS for error endpoints
174+
this.app.use(createErrorCORSMiddleware());
175+
176+
// Add combined error middleware
177+
const errorMiddleware = createCombinedErrorMiddleware({
178+
errorStore,
179+
logger,
180+
projectRoot,
181+
logToConsole,
182+
});
183+
this.app.use(errorMiddleware);
184+
185+
// Health check endpoint
186+
this.app.get('/_dev/health', (_req, res) => {
187+
res.json({
188+
status: 'ok',
189+
service: 'error-capture',
190+
uptime: process.uptime(),
191+
errors: errorStore.getErrorCount(),
192+
});
193+
});
194+
195+
// Catch-all for unknown routes
196+
this.app.use((_req, res) => {
197+
res.status(404).json({
198+
error: 'Not Found',
199+
message: 'Endpoint not found. Available endpoints: POST/GET/DELETE /_dev/errors, GET /_dev/errors/stats',
200+
});
201+
});
202+
}
203+
}
204+
205+
/**
206+
* Convenience function to start an error capture server
207+
*
208+
* @param config - Server configuration
209+
* @returns Running ErrorCaptureServer instance
210+
*/
211+
export async function startErrorCaptureServer(config: ErrorServerConfig): Promise<ErrorCaptureServer> {
212+
const server = new ErrorCaptureServer(config);
213+
await server.start();
214+
return server;
215+
}

0 commit comments

Comments
 (0)