@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 +39 -0
- package/src/index.ts +273 -0
- package/src/instrumentation.ts +146 -0
- package/src/panel.ts +288 -0
- package/tsconfig.json +17 -0
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
|
+
}
|