@flight-framework/devtools 0.0.1

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/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@flight-framework/devtools",
3
+ "version": "0.0.1",
4
+ "description": "Development tools and debugging panel for Flight Framework",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./panel": {
14
+ "types": "./dist/panel.d.ts",
15
+ "import": "./dist/panel.js"
16
+ },
17
+ "./instrumentation": {
18
+ "types": "./dist/instrumentation.d.ts",
19
+ "import": "./dist/instrumentation.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "dev": "tsc --watch"
25
+ },
26
+ "peerDependencies": {
27
+ "@flight-framework/core": ">=0.0.1"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.7.0"
31
+ },
32
+ "keywords": [
33
+ "flight",
34
+ "devtools",
35
+ "debugging",
36
+ "development"
37
+ ],
38
+ "license": "MIT"
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,273 @@
1
+ /**
2
+ * @flight-framework/devtools - Development Tools
3
+ *
4
+ * Provides debugging tools for Flight applications during development.
5
+ * Automatically disabled in production builds.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // In your flight.config.ts or main entry
10
+ * import { enableDevTools } from '@flight-framework/devtools';
11
+ *
12
+ * if (process.env.NODE_ENV === 'development') {
13
+ * enableDevTools({
14
+ * port: 3001,
15
+ * showRoutes: true,
16
+ * showRequests: true,
17
+ * showCache: true,
18
+ * });
19
+ * }
20
+ * ```
21
+ */
22
+
23
+ // ============================================================================
24
+ // Types
25
+ // ============================================================================
26
+
27
+ export interface DevToolsOptions {
28
+ /** WebSocket port for DevTools communication (default: 3001) */
29
+ port?: number;
30
+ /** Show registered routes */
31
+ showRoutes?: boolean;
32
+ /** Show incoming requests */
33
+ showRequests?: boolean;
34
+ /** Show cache status */
35
+ showCache?: boolean;
36
+ /** Show connected adapters */
37
+ showAdapters?: boolean;
38
+ /** Custom panel position */
39
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
40
+ }
41
+
42
+ export interface RouteInfo {
43
+ method: string;
44
+ path: string;
45
+ type: 'page' | 'api';
46
+ filePath: string;
47
+ slot?: string;
48
+ interceptInfo?: {
49
+ level: number;
50
+ target: string;
51
+ };
52
+ }
53
+
54
+ export interface RequestInfo {
55
+ id: string;
56
+ method: string;
57
+ path: string;
58
+ status: number;
59
+ duration: number;
60
+ timestamp: number;
61
+ }
62
+
63
+ export interface CacheInfo {
64
+ key: string;
65
+ size: number;
66
+ hits: number;
67
+ lastAccessed: number;
68
+ }
69
+
70
+ export interface AdapterInfo {
71
+ name: string;
72
+ type: 'db' | 'auth' | 'storage' | 'email' | 'payments' | 'realtime' | 'other';
73
+ status: 'connected' | 'disconnected' | 'error';
74
+ }
75
+
76
+ export interface DevToolsState {
77
+ routes: RouteInfo[];
78
+ requests: RequestInfo[];
79
+ cache: CacheInfo[];
80
+ adapters: AdapterInfo[];
81
+ }
82
+
83
+ // ============================================================================
84
+ // DevTools Manager
85
+ // ============================================================================
86
+
87
+ let devToolsInstance: DevToolsManager | null = null;
88
+
89
+ class DevToolsManager {
90
+ private options: Required<DevToolsOptions>;
91
+ private state: DevToolsState;
92
+ private subscribers: Set<(state: DevToolsState) => void>;
93
+ private isEnabled: boolean;
94
+
95
+ constructor(options: DevToolsOptions) {
96
+ this.options = {
97
+ port: options.port ?? 3001,
98
+ showRoutes: options.showRoutes ?? true,
99
+ showRequests: options.showRequests ?? true,
100
+ showCache: options.showCache ?? true,
101
+ showAdapters: options.showAdapters ?? true,
102
+ position: options.position ?? 'bottom-right',
103
+ };
104
+
105
+ this.state = {
106
+ routes: [],
107
+ requests: [],
108
+ cache: [],
109
+ adapters: [],
110
+ };
111
+
112
+ this.subscribers = new Set();
113
+ this.isEnabled = process.env.NODE_ENV === 'development';
114
+ }
115
+
116
+ /**
117
+ * Register routes for display in DevTools
118
+ */
119
+ registerRoutes(routes: RouteInfo[]): void {
120
+ if (!this.isEnabled) return;
121
+ this.state.routes = routes;
122
+ this.notify();
123
+ }
124
+
125
+ /**
126
+ * Log a request
127
+ */
128
+ logRequest(info: Omit<RequestInfo, 'id' | 'timestamp'>): void {
129
+ if (!this.isEnabled || !this.options.showRequests) return;
130
+
131
+ const request: RequestInfo = {
132
+ ...info,
133
+ id: crypto.randomUUID(),
134
+ timestamp: Date.now(),
135
+ };
136
+
137
+ // Keep last 100 requests
138
+ this.state.requests = [request, ...this.state.requests].slice(0, 100);
139
+ this.notify();
140
+ }
141
+
142
+ /**
143
+ * Update cache info
144
+ */
145
+ updateCache(cache: CacheInfo[]): void {
146
+ if (!this.isEnabled || !this.options.showCache) return;
147
+ this.state.cache = cache;
148
+ this.notify();
149
+ }
150
+
151
+ /**
152
+ * Register an adapter
153
+ */
154
+ registerAdapter(adapter: AdapterInfo): void {
155
+ if (!this.isEnabled || !this.options.showAdapters) return;
156
+
157
+ const existing = this.state.adapters.findIndex(a => a.name === adapter.name);
158
+ if (existing >= 0) {
159
+ this.state.adapters[existing] = adapter;
160
+ } else {
161
+ this.state.adapters.push(adapter);
162
+ }
163
+ this.notify();
164
+ }
165
+
166
+ /**
167
+ * Subscribe to state changes
168
+ */
169
+ subscribe(callback: (state: DevToolsState) => void): () => void {
170
+ this.subscribers.add(callback);
171
+ callback(this.state); // Initial state
172
+ return () => this.subscribers.delete(callback);
173
+ }
174
+
175
+ /**
176
+ * Get current state
177
+ */
178
+ getState(): DevToolsState {
179
+ return this.state;
180
+ }
181
+
182
+ /**
183
+ * Get options
184
+ */
185
+ getOptions(): Required<DevToolsOptions> {
186
+ return this.options;
187
+ }
188
+
189
+ private notify(): void {
190
+ for (const callback of this.subscribers) {
191
+ callback(this.state);
192
+ }
193
+ }
194
+ }
195
+
196
+ // ============================================================================
197
+ // Public API
198
+ // ============================================================================
199
+
200
+ /**
201
+ * Enable DevTools for the application
202
+ */
203
+ export function enableDevTools(options: DevToolsOptions = {}): DevToolsManager {
204
+ if (process.env.NODE_ENV !== 'development') {
205
+ console.warn('[Flight DevTools] DevTools are disabled in production');
206
+ return devToolsInstance as DevToolsManager;
207
+ }
208
+
209
+ if (!devToolsInstance) {
210
+ devToolsInstance = new DevToolsManager(options);
211
+ console.log(`[Flight DevTools] Enabled on port ${options.port ?? 3001}`);
212
+ }
213
+
214
+ return devToolsInstance;
215
+ }
216
+
217
+ /**
218
+ * Get the DevTools instance
219
+ */
220
+ export function getDevTools(): DevToolsManager | null {
221
+ return devToolsInstance;
222
+ }
223
+
224
+ /**
225
+ * Check if DevTools are enabled
226
+ */
227
+ export function isDevToolsEnabled(): boolean {
228
+ return devToolsInstance !== null && process.env.NODE_ENV === 'development';
229
+ }
230
+
231
+ // ============================================================================
232
+ // Instrumentation Helpers
233
+ // ============================================================================
234
+
235
+ /**
236
+ * Middleware to log requests to DevTools
237
+ */
238
+ export function devToolsMiddleware() {
239
+ return async (
240
+ context: { request: Request },
241
+ next: () => Promise<Response>
242
+ ): Promise<Response> => {
243
+ if (!devToolsInstance) return next();
244
+
245
+ const start = performance.now();
246
+ const { request } = context;
247
+
248
+ try {
249
+ const response = await next();
250
+ const duration = performance.now() - start;
251
+
252
+ devToolsInstance.logRequest({
253
+ method: request.method,
254
+ path: new URL(request.url).pathname,
255
+ status: response.status,
256
+ duration: Math.round(duration),
257
+ });
258
+
259
+ return response;
260
+ } catch (error) {
261
+ const duration = performance.now() - start;
262
+
263
+ devToolsInstance.logRequest({
264
+ method: request.method,
265
+ path: new URL(request.url).pathname,
266
+ status: 500,
267
+ duration: Math.round(duration),
268
+ });
269
+
270
+ throw error;
271
+ }
272
+ };
273
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @flight-framework/devtools/instrumentation - Auto-instrumentation
3
+ *
4
+ * Automatically instruments Flight apps to send data to DevTools.
5
+ */
6
+
7
+ import { getDevTools, type RouteInfo, type AdapterInfo } from './index.js';
8
+
9
+ // ============================================================================
10
+ // Route Instrumentation
11
+ // ============================================================================
12
+
13
+ /**
14
+ * Instrument file router to report routes to DevTools
15
+ */
16
+ export function instrumentFileRouter(routes: unknown[]): void {
17
+ const devTools = getDevTools();
18
+ if (!devTools) return;
19
+
20
+ const routeInfos: RouteInfo[] = (routes as any[]).map(route => ({
21
+ method: route.method || 'GET',
22
+ path: route.path || '/',
23
+ type: route.type || 'page',
24
+ filePath: route.filePath || '',
25
+ slot: route.slot,
26
+ interceptInfo: route.interceptInfo,
27
+ }));
28
+
29
+ devTools.registerRoutes(routeInfos);
30
+ }
31
+
32
+ // ============================================================================
33
+ // Adapter Instrumentation
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Instrument an adapter to report to DevTools
38
+ */
39
+ export function instrumentAdapter(
40
+ adapter: { name: string },
41
+ type: AdapterInfo['type']
42
+ ): void {
43
+ const devTools = getDevTools();
44
+ if (!devTools) return;
45
+
46
+ devTools.registerAdapter({
47
+ name: adapter.name,
48
+ type,
49
+ status: 'connected',
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Report adapter error to DevTools
55
+ */
56
+ export function reportAdapterError(adapterName: string): void {
57
+ const devTools = getDevTools();
58
+ if (!devTools) return;
59
+
60
+ const state = devTools.getState();
61
+ const adapter = state.adapters.find(a => a.name === adapterName);
62
+
63
+ if (adapter) {
64
+ devTools.registerAdapter({
65
+ ...adapter,
66
+ status: 'error',
67
+ });
68
+ }
69
+ }
70
+
71
+ // ============================================================================
72
+ // Cache Instrumentation
73
+ // ============================================================================
74
+
75
+ /**
76
+ * Instrument a cache to report to DevTools
77
+ */
78
+ export function instrumentCache(cache: Map<string, unknown>): void {
79
+ const devTools = getDevTools();
80
+ if (!devTools) return;
81
+
82
+ const cacheInfos = Array.from(cache.entries()).map(([key, value]) => ({
83
+ key,
84
+ size: JSON.stringify(value).length,
85
+ hits: 0,
86
+ lastAccessed: Date.now(),
87
+ }));
88
+
89
+ devTools.updateCache(cacheInfos);
90
+ }
91
+
92
+ // ============================================================================
93
+ // Auto-Instrumentation
94
+ // ============================================================================
95
+
96
+ /**
97
+ * Auto-instrument common Flight patterns
98
+ * Call this once during app initialization
99
+ */
100
+ export function autoInstrument(): void {
101
+ if (process.env.NODE_ENV !== 'development') return;
102
+
103
+ console.log('[Flight DevTools] Auto-instrumentation enabled');
104
+
105
+ // Instrument fetch for request logging
106
+ if (typeof globalThis.fetch !== 'undefined') {
107
+ const originalFetch = globalThis.fetch;
108
+
109
+ globalThis.fetch = async (input, init) => {
110
+ const devTools = getDevTools();
111
+ const start = performance.now();
112
+
113
+ try {
114
+ const response = await originalFetch(input, init);
115
+
116
+ if (devTools) {
117
+ const url = typeof input === 'string'
118
+ ? input
119
+ : input instanceof URL
120
+ ? input.pathname
121
+ : (input as Request).url;
122
+
123
+ devTools.logRequest({
124
+ method: init?.method || 'GET',
125
+ path: new URL(url, 'http://localhost').pathname,
126
+ status: response.status,
127
+ duration: Math.round(performance.now() - start),
128
+ });
129
+ }
130
+
131
+ return response;
132
+ } catch (error) {
133
+ if (devTools) {
134
+ const url = typeof input === 'string' ? input : (input as Request)?.url || '/';
135
+ devTools.logRequest({
136
+ method: init?.method || 'GET',
137
+ path: new URL(url, 'http://localhost').pathname,
138
+ status: 0,
139
+ duration: Math.round(performance.now() - start),
140
+ });
141
+ }
142
+ throw error;
143
+ }
144
+ };
145
+ }
146
+ }
package/src/panel.ts ADDED
@@ -0,0 +1,288 @@
1
+ /**
2
+ * @flight-framework/devtools/panel - DevTools Panel UI
3
+ *
4
+ * React component for the DevTools floating panel.
5
+ * Automatically injected into pages during development.
6
+ */
7
+
8
+ import { getDevTools, type DevToolsState, type RouteInfo, type RequestInfo } from './index.js';
9
+
10
+ // ============================================================================
11
+ // Panel Component (Plain JS for framework-agnostic use)
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Inject the DevTools panel into the page
16
+ * Call this in your app's entry point during development
17
+ */
18
+ export function injectDevToolsPanel(): void {
19
+ if (typeof document === 'undefined') return;
20
+
21
+ const devTools = getDevTools();
22
+ if (!devTools) return;
23
+
24
+ // Create container
25
+ const container = document.createElement('div');
26
+ container.id = 'flight-devtools';
27
+ document.body.appendChild(container);
28
+
29
+ // Inject styles
30
+ const styles = document.createElement('style');
31
+ styles.textContent = getDevToolsStyles();
32
+ document.head.appendChild(styles);
33
+
34
+ // Initial render
35
+ let isOpen = false;
36
+ let currentTab = 'routes';
37
+
38
+ function render(state: DevToolsState): void {
39
+ const options = devTools!.getOptions();
40
+
41
+ container.innerHTML = `
42
+ <div class="flight-devtools ${options.position} ${isOpen ? 'open' : ''}">
43
+ <button class="flight-devtools-toggle" onclick="window.__flightToggleDevTools()">
44
+ ${isOpen ? 'X' : 'Flight'}
45
+ </button>
46
+ ${isOpen ? renderPanel(state, currentTab) : ''}
47
+ </div>
48
+ `;
49
+ }
50
+
51
+ function renderPanel(state: DevToolsState, tab: string): string {
52
+ return `
53
+ <div class="flight-devtools-panel">
54
+ <div class="flight-devtools-header">
55
+ <span>Flight DevTools</span>
56
+ </div>
57
+ <div class="flight-devtools-tabs">
58
+ <button class="${tab === 'routes' ? 'active' : ''}" onclick="window.__flightSetTab('routes')">
59
+ Routes (${state.routes.length})
60
+ </button>
61
+ <button class="${tab === 'requests' ? 'active' : ''}" onclick="window.__flightSetTab('requests')">
62
+ Requests (${state.requests.length})
63
+ </button>
64
+ <button class="${tab === 'adapters' ? 'active' : ''}" onclick="window.__flightSetTab('adapters')">
65
+ Adapters (${state.adapters.length})
66
+ </button>
67
+ </div>
68
+ <div class="flight-devtools-content">
69
+ ${tab === 'routes' ? renderRoutes(state.routes) : ''}
70
+ ${tab === 'requests' ? renderRequests(state.requests) : ''}
71
+ ${tab === 'adapters' ? renderAdapters(state) : ''}
72
+ </div>
73
+ </div>
74
+ `;
75
+ }
76
+
77
+ function renderRoutes(routes: RouteInfo[]): string {
78
+ if (routes.length === 0) {
79
+ return '<div class="flight-devtools-empty">No routes registered</div>';
80
+ }
81
+
82
+ const dots = (level: number) => '.'.repeat(level);
83
+
84
+ return `
85
+ <div class="flight-devtools-list">
86
+ ${routes.map(route => `
87
+ <div class="flight-devtools-item">
88
+ <span class="method ${route.method.toLowerCase()}">${route.method}</span>
89
+ <span class="path">${route.path}</span>
90
+ <span class="type">${route.type}</span>
91
+ ${route.slot ? `<span class="slot">@${route.slot}</span>` : ''}
92
+ ${route.interceptInfo ? `<span class="intercept">(${dots(route.interceptInfo.level)})</span>` : ''}
93
+ </div>
94
+ `).join('')}
95
+ </div>
96
+ `;
97
+ }
98
+
99
+ function renderRequests(requests: RequestInfo[]): string {
100
+ if (requests.length === 0) {
101
+ return '<div class="flight-devtools-empty">No requests yet</div>';
102
+ }
103
+
104
+ return `
105
+ <div class="flight-devtools-list">
106
+ ${requests.slice(0, 20).map(req => `
107
+ <div class="flight-devtools-item">
108
+ <span class="method ${req.method.toLowerCase()}">${req.method}</span>
109
+ <span class="path">${req.path}</span>
110
+ <span class="status status-${Math.floor(req.status / 100)}xx">${req.status}</span>
111
+ <span class="duration">${req.duration}ms</span>
112
+ </div>
113
+ `).join('')}
114
+ </div>
115
+ `;
116
+ }
117
+
118
+ function renderAdapters(state: DevToolsState): string {
119
+ if (state.adapters.length === 0) {
120
+ return '<div class="flight-devtools-empty">No adapters registered</div>';
121
+ }
122
+
123
+ return `
124
+ <div class="flight-devtools-list">
125
+ ${state.adapters.map(adapter => `
126
+ <div class="flight-devtools-item">
127
+ <span class="adapter-type">${adapter.type}</span>
128
+ <span class="adapter-name">${adapter.name}</span>
129
+ <span class="adapter-status status-${adapter.status}">${adapter.status}</span>
130
+ </div>
131
+ `).join('')}
132
+ </div>
133
+ `;
134
+ }
135
+
136
+ // Global functions for onclick handlers
137
+ (window as any).__flightToggleDevTools = () => {
138
+ isOpen = !isOpen;
139
+ render(devTools!.getState());
140
+ };
141
+
142
+ (window as any).__flightSetTab = (tab: string) => {
143
+ currentTab = tab;
144
+ render(devTools!.getState());
145
+ };
146
+
147
+ // Subscribe to state changes
148
+ devTools.subscribe(render);
149
+ }
150
+
151
+ // ============================================================================
152
+ // Styles
153
+ // ============================================================================
154
+
155
+ function getDevToolsStyles(): string {
156
+ return `
157
+ #flight-devtools {
158
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
159
+ font-size: 12px;
160
+ }
161
+
162
+ .flight-devtools {
163
+ position: fixed;
164
+ z-index: 99999;
165
+ }
166
+
167
+ .flight-devtools.bottom-right {
168
+ bottom: 16px;
169
+ right: 16px;
170
+ }
171
+
172
+ .flight-devtools.bottom-left {
173
+ bottom: 16px;
174
+ left: 16px;
175
+ }
176
+
177
+ .flight-devtools-toggle {
178
+ width: 48px;
179
+ height: 48px;
180
+ border-radius: 50%;
181
+ border: none;
182
+ background: #32CD32;
183
+ color: white;
184
+ font-weight: bold;
185
+ cursor: pointer;
186
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
187
+ }
188
+
189
+ .flight-devtools.open .flight-devtools-toggle {
190
+ border-radius: 50%;
191
+ position: absolute;
192
+ top: 8px;
193
+ right: 8px;
194
+ width: 32px;
195
+ height: 32px;
196
+ font-size: 14px;
197
+ }
198
+
199
+ .flight-devtools-panel {
200
+ position: absolute;
201
+ bottom: 60px;
202
+ right: 0;
203
+ width: 400px;
204
+ max-height: 500px;
205
+ background: #1a1a1a;
206
+ border-radius: 12px;
207
+ overflow: hidden;
208
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
209
+ }
210
+
211
+ .flight-devtools-header {
212
+ padding: 12px 16px;
213
+ background: #32CD32;
214
+ color: white;
215
+ font-weight: 600;
216
+ }
217
+
218
+ .flight-devtools-tabs {
219
+ display: flex;
220
+ border-bottom: 1px solid #333;
221
+ }
222
+
223
+ .flight-devtools-tabs button {
224
+ flex: 1;
225
+ padding: 8px;
226
+ border: none;
227
+ background: transparent;
228
+ color: #888;
229
+ cursor: pointer;
230
+ }
231
+
232
+ .flight-devtools-tabs button.active {
233
+ color: #32CD32;
234
+ border-bottom: 2px solid #32CD32;
235
+ }
236
+
237
+ .flight-devtools-content {
238
+ max-height: 350px;
239
+ overflow-y: auto;
240
+ }
241
+
242
+ .flight-devtools-list {
243
+ padding: 8px;
244
+ }
245
+
246
+ .flight-devtools-item {
247
+ display: flex;
248
+ gap: 8px;
249
+ padding: 8px;
250
+ border-radius: 4px;
251
+ color: #ccc;
252
+ }
253
+
254
+ .flight-devtools-item:hover {
255
+ background: #252525;
256
+ }
257
+
258
+ .flight-devtools-item .method {
259
+ width: 50px;
260
+ font-weight: 600;
261
+ }
262
+
263
+ .flight-devtools-item .method.get { color: #4CAF50; }
264
+ .flight-devtools-item .method.post { color: #2196F3; }
265
+ .flight-devtools-item .method.put { color: #FF9800; }
266
+ .flight-devtools-item .method.delete { color: #f44336; }
267
+
268
+ .flight-devtools-item .path {
269
+ flex: 1;
270
+ font-family: monospace;
271
+ }
272
+
273
+ .flight-devtools-item .status-2xx { color: #4CAF50; }
274
+ .flight-devtools-item .status-3xx { color: #FF9800; }
275
+ .flight-devtools-item .status-4xx { color: #f44336; }
276
+ .flight-devtools-item .status-5xx { color: #9C27B0; }
277
+
278
+ .flight-devtools-item .duration {
279
+ color: #888;
280
+ }
281
+
282
+ .flight-devtools-empty {
283
+ padding: 32px;
284
+ text-align: center;
285
+ color: #666;
286
+ }
287
+ `;
288
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "jsx": "react-jsx"
9
+ },
10
+ "include": [
11
+ "src/**/*"
12
+ ],
13
+ "exclude": [
14
+ "node_modules",
15
+ "dist"
16
+ ]
17
+ }