@theihtisham/devtools-with-cloud 1.0.0
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/.env.example +15 -0
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/docker-compose.yml +23 -0
- package/jest.config.js +7 -0
- package/next-env.d.ts +5 -0
- package/next.config.mjs +22 -0
- package/package.json +82 -0
- package/postcss.config.js +6 -0
- package/prisma/schema.prisma +105 -0
- package/prisma/seed.ts +211 -0
- package/src/app/(app)/ai/page.tsx +122 -0
- package/src/app/(app)/collections/page.tsx +155 -0
- package/src/app/(app)/environments/page.tsx +96 -0
- package/src/app/(app)/history/page.tsx +107 -0
- package/src/app/(app)/import/page.tsx +102 -0
- package/src/app/(app)/layout.tsx +60 -0
- package/src/app/(app)/settings/page.tsx +79 -0
- package/src/app/(app)/workspace/page.tsx +284 -0
- package/src/app/api/ai/discover/route.ts +17 -0
- package/src/app/api/ai/explain/route.ts +29 -0
- package/src/app/api/ai/generate-tests/route.ts +37 -0
- package/src/app/api/ai/suggest/route.ts +29 -0
- package/src/app/api/collections/[id]/route.ts +66 -0
- package/src/app/api/collections/route.ts +48 -0
- package/src/app/api/environments/route.ts +40 -0
- package/src/app/api/export/openapi/route.ts +17 -0
- package/src/app/api/export/postman/route.ts +18 -0
- package/src/app/api/import/curl/route.ts +18 -0
- package/src/app/api/import/har/route.ts +20 -0
- package/src/app/api/import/openapi/route.ts +21 -0
- package/src/app/api/import/postman/route.ts +21 -0
- package/src/app/api/proxy/route.ts +35 -0
- package/src/app/api/requests/[id]/execute/route.ts +85 -0
- package/src/app/api/requests/[id]/history/route.ts +23 -0
- package/src/app/api/requests/[id]/route.ts +66 -0
- package/src/app/api/requests/route.ts +49 -0
- package/src/app/api/workspaces/route.ts +38 -0
- package/src/app/globals.css +99 -0
- package/src/app/layout.tsx +24 -0
- package/src/app/page.tsx +182 -0
- package/src/components/ai/ai-panel.tsx +65 -0
- package/src/components/ai/code-explainer.tsx +51 -0
- package/src/components/ai/endpoint-discovery.tsx +62 -0
- package/src/components/ai/test-generator.tsx +49 -0
- package/src/components/collections/collection-actions.tsx +36 -0
- package/src/components/collections/collection-tree.tsx +55 -0
- package/src/components/collections/folder-creator.tsx +54 -0
- package/src/components/landing/comparison.tsx +43 -0
- package/src/components/landing/cta.tsx +16 -0
- package/src/components/landing/features.tsx +24 -0
- package/src/components/landing/hero.tsx +23 -0
- package/src/components/response/body-viewer.tsx +33 -0
- package/src/components/response/headers-viewer.tsx +23 -0
- package/src/components/response/status-badge.tsx +25 -0
- package/src/components/response/test-results.tsx +50 -0
- package/src/components/response/timing-chart.tsx +39 -0
- package/src/components/ui/badge.tsx +24 -0
- package/src/components/ui/button.tsx +32 -0
- package/src/components/ui/code-editor.tsx +51 -0
- package/src/components/ui/dialog.tsx +56 -0
- package/src/components/ui/dropdown.tsx +63 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/key-value-editor.tsx +75 -0
- package/src/components/ui/select.tsx +24 -0
- package/src/components/ui/tabs.tsx +85 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/toast.tsx +54 -0
- package/src/components/workspace/request-panel.tsx +38 -0
- package/src/components/workspace/response-panel.tsx +81 -0
- package/src/components/workspace/sidebar.tsx +52 -0
- package/src/components/workspace/split-pane.tsx +49 -0
- package/src/components/workspace/tabs/auth-tab.tsx +94 -0
- package/src/components/workspace/tabs/body-tab.tsx +41 -0
- package/src/components/workspace/tabs/headers-tab.tsx +23 -0
- package/src/components/workspace/tabs/params-tab.tsx +23 -0
- package/src/components/workspace/tabs/pre-request-tab.tsx +26 -0
- package/src/components/workspace/url-bar.tsx +53 -0
- package/src/hooks/use-ai.ts +115 -0
- package/src/hooks/use-collection.ts +71 -0
- package/src/hooks/use-environment.ts +73 -0
- package/src/hooks/use-request.ts +111 -0
- package/src/lib/ai/endpoint-discovery.ts +158 -0
- package/src/lib/ai/explainer.ts +127 -0
- package/src/lib/ai/suggester.ts +164 -0
- package/src/lib/ai/test-generator.ts +161 -0
- package/src/lib/auth/api-key.ts +28 -0
- package/src/lib/auth/aws-sig.ts +131 -0
- package/src/lib/auth/basic.ts +17 -0
- package/src/lib/auth/bearer.ts +15 -0
- package/src/lib/auth/oauth2.ts +155 -0
- package/src/lib/auth/types.ts +16 -0
- package/src/lib/db/client.ts +15 -0
- package/src/lib/env/manager.ts +32 -0
- package/src/lib/env/resolver.ts +30 -0
- package/src/lib/exporters/openapi.ts +193 -0
- package/src/lib/exporters/postman.ts +140 -0
- package/src/lib/graphql/builder.ts +249 -0
- package/src/lib/graphql/formatter.ts +147 -0
- package/src/lib/graphql/index.ts +43 -0
- package/src/lib/graphql/introspection.ts +175 -0
- package/src/lib/graphql/types.ts +99 -0
- package/src/lib/graphql/validator.ts +216 -0
- package/src/lib/http/client.ts +112 -0
- package/src/lib/http/proxy.ts +83 -0
- package/src/lib/http/request-builder.ts +214 -0
- package/src/lib/http/response-parser.ts +106 -0
- package/src/lib/http/timing.ts +63 -0
- package/src/lib/importers/curl-parser.ts +346 -0
- package/src/lib/importers/har-parser.ts +128 -0
- package/src/lib/importers/openapi.ts +324 -0
- package/src/lib/importers/postman.ts +312 -0
- package/src/lib/test-runner/assertions.ts +163 -0
- package/src/lib/test-runner/reporter.ts +90 -0
- package/src/lib/test-runner/runner.ts +69 -0
- package/src/lib/utils/api-response.ts +85 -0
- package/src/lib/utils/cn.ts +6 -0
- package/src/lib/utils/content-type.ts +123 -0
- package/src/lib/utils/download.ts +53 -0
- package/src/lib/utils/errors.ts +92 -0
- package/src/lib/utils/format.ts +142 -0
- package/src/lib/utils/syntax-highlight.ts +108 -0
- package/src/lib/utils/validation.ts +231 -0
- package/src/lib/websocket/client.ts +182 -0
- package/src/lib/websocket/frames.ts +96 -0
- package/src/lib/websocket/history.ts +121 -0
- package/src/lib/websocket/index.ts +25 -0
- package/src/lib/websocket/types.ts +57 -0
- package/src/types/ai.ts +28 -0
- package/src/types/collection.ts +24 -0
- package/src/types/environment.ts +16 -0
- package/src/types/request.ts +54 -0
- package/src/types/response.ts +37 -0
- package/tailwind.config.ts +82 -0
- package/tests/lib/env/resolver.test.ts +108 -0
- package/tests/lib/graphql/builder.test.ts +349 -0
- package/tests/lib/graphql/formatter.test.ts +99 -0
- package/tests/lib/http/request-builder.test.ts +160 -0
- package/tests/lib/http/response-parser.test.ts +150 -0
- package/tests/lib/http/timing.test.ts +188 -0
- package/tests/lib/importers/curl-parser.test.ts +245 -0
- package/tests/lib/test-runner/assertions.test.ts +342 -0
- package/tests/lib/utils/cn.test.ts +46 -0
- package/tests/lib/utils/content-type.test.ts +175 -0
- package/tests/lib/utils/format.test.ts +188 -0
- package/tests/lib/utils/validation.test.ts +237 -0
- package/tests/lib/websocket/history.test.ts +186 -0
- package/tsconfig.json +29 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Frame Utilities — parse, format, and detect WebSocket frame types.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { WSFrame, WSMessageType } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse raw data into a WSFrame representation.
|
|
9
|
+
* Note: This provides a simplified frame for display purposes.
|
|
10
|
+
* Full frame parsing requires a proper WebSocket frame decoder.
|
|
11
|
+
*/
|
|
12
|
+
export function parseFrame(data: string | ArrayBuffer): WSFrame {
|
|
13
|
+
if (typeof data === 'string') {
|
|
14
|
+
return {
|
|
15
|
+
isFinal: true,
|
|
16
|
+
opcode: 0x01, // text frame
|
|
17
|
+
masked: false,
|
|
18
|
+
payloadLength: new TextEncoder().encode(data).length,
|
|
19
|
+
payload: data,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Binary data
|
|
24
|
+
const view = new Uint8Array(data);
|
|
25
|
+
return {
|
|
26
|
+
isFinal: true,
|
|
27
|
+
opcode: 0x02, // binary frame
|
|
28
|
+
masked: false,
|
|
29
|
+
payloadLength: view.length,
|
|
30
|
+
payload: `[Binary: ${view.length} bytes]`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Format a message for display in the UI.
|
|
36
|
+
*/
|
|
37
|
+
export function formatMessage(data: string, type: WSMessageType): string {
|
|
38
|
+
switch (type) {
|
|
39
|
+
case 'json':
|
|
40
|
+
try {
|
|
41
|
+
return JSON.stringify(JSON.parse(data), null, 2);
|
|
42
|
+
} catch {
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
case 'binary':
|
|
46
|
+
return `[Binary data: ${data.length} chars]`;
|
|
47
|
+
case 'ping':
|
|
48
|
+
return `[PING] ${data}`;
|
|
49
|
+
case 'pong':
|
|
50
|
+
return `[PONG] ${data}`;
|
|
51
|
+
case 'text':
|
|
52
|
+
default:
|
|
53
|
+
return data;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Auto-detect the type of a WebSocket message.
|
|
59
|
+
*/
|
|
60
|
+
export function detectMessageType(data: string): WSMessageType {
|
|
61
|
+
if (!data) return 'text';
|
|
62
|
+
|
|
63
|
+
// Try JSON parse
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(data);
|
|
66
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
67
|
+
return 'json';
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Not JSON
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return 'text';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get a human-readable opcode name.
|
|
78
|
+
*/
|
|
79
|
+
export function getOpcodeName(opcode: number): string {
|
|
80
|
+
const opcodes: Record<number, string> = {
|
|
81
|
+
0x00: 'continuation',
|
|
82
|
+
0x01: 'text',
|
|
83
|
+
0x02: 'binary',
|
|
84
|
+
0x08: 'close',
|
|
85
|
+
0x09: 'ping',
|
|
86
|
+
0x0a: 'pong',
|
|
87
|
+
};
|
|
88
|
+
return opcodes[opcode] ?? `unknown(0x${opcode.toString(16)})`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Calculate the byte size of a string message.
|
|
93
|
+
*/
|
|
94
|
+
export function getByteSize(data: string): number {
|
|
95
|
+
return new TextEncoder().encode(data).length;
|
|
96
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Message History — records, searches, and exports WebSocket messages.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { WSMessage } from './types';
|
|
6
|
+
|
|
7
|
+
export class MessageHistory {
|
|
8
|
+
private messages: WSMessage[] = [];
|
|
9
|
+
private maxSize: number;
|
|
10
|
+
|
|
11
|
+
constructor(maxSize: number = 1000) {
|
|
12
|
+
this.maxSize = maxSize;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Record a message (sent or received).
|
|
17
|
+
* Oldest messages are evicted when maxSize is exceeded.
|
|
18
|
+
*/
|
|
19
|
+
add(message: WSMessage): void {
|
|
20
|
+
this.messages.push(message);
|
|
21
|
+
|
|
22
|
+
// Evict oldest messages if over capacity
|
|
23
|
+
while (this.messages.length > this.maxSize) {
|
|
24
|
+
this.messages.shift();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get all recorded messages.
|
|
30
|
+
*/
|
|
31
|
+
getHistory(): WSMessage[] {
|
|
32
|
+
return [...this.messages];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get messages filtered by direction.
|
|
37
|
+
*/
|
|
38
|
+
getByDirection(direction: 'sent' | 'received'): WSMessage[] {
|
|
39
|
+
return this.messages.filter((m) => m.direction === direction);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get messages within a time range.
|
|
44
|
+
*/
|
|
45
|
+
getByTimeRange(start: number, end: number): WSMessage[] {
|
|
46
|
+
return this.messages.filter((m) => m.timestamp >= start && m.timestamp <= end);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the N most recent messages.
|
|
51
|
+
*/
|
|
52
|
+
getRecent(count: number): WSMessage[] {
|
|
53
|
+
return this.messages.slice(-count);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clear all recorded messages.
|
|
58
|
+
*/
|
|
59
|
+
clear(): void {
|
|
60
|
+
this.messages = [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the total number of messages.
|
|
65
|
+
*/
|
|
66
|
+
get count(): number {
|
|
67
|
+
return this.messages.length;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get total bytes across all messages.
|
|
72
|
+
*/
|
|
73
|
+
get totalBytes(): number {
|
|
74
|
+
return this.messages.reduce((sum, m) => sum + m.size, 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Search messages by content (substring match).
|
|
79
|
+
*/
|
|
80
|
+
search(query: string): WSMessage[] {
|
|
81
|
+
const lowerQuery = query.toLowerCase();
|
|
82
|
+
return this.messages.filter((m) => m.data.toLowerCase().includes(lowerQuery));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Search messages by type.
|
|
87
|
+
*/
|
|
88
|
+
searchByType(type: WSMessage['type']): WSMessage[] {
|
|
89
|
+
return this.messages.filter((m) => m.type === type);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Export all messages as a JSON string.
|
|
94
|
+
*/
|
|
95
|
+
export(): string {
|
|
96
|
+
return JSON.stringify(this.messages, null, 2);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Export messages as a JSON array (for programmatic use).
|
|
101
|
+
*/
|
|
102
|
+
toArray(): WSMessage[] {
|
|
103
|
+
return this.messages.map((m) => ({ ...m }));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Import messages from a JSON string.
|
|
108
|
+
*/
|
|
109
|
+
import(json: string): void {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(json) as WSMessage[];
|
|
112
|
+
if (Array.isArray(parsed)) {
|
|
113
|
+
for (const msg of parsed) {
|
|
114
|
+
this.add(msg);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
throw new Error('Invalid JSON: cannot import message history.');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket module — client, message history, and frame utilities.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
WSConfig,
|
|
7
|
+
WSConnectionState,
|
|
8
|
+
WSMessage,
|
|
9
|
+
WSFrame,
|
|
10
|
+
WSConnection,
|
|
11
|
+
WSClientEvents,
|
|
12
|
+
WSMessageType,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
export { WebSocketClient } from './client';
|
|
16
|
+
|
|
17
|
+
export { MessageHistory } from './history';
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
parseFrame,
|
|
21
|
+
formatMessage,
|
|
22
|
+
detectMessageType,
|
|
23
|
+
getOpcodeName,
|
|
24
|
+
getByteSize,
|
|
25
|
+
} from './frames';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket types for the testing client.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type WSConnectionState = 'connecting' | 'connected' | 'disconnecting' | 'disconnected';
|
|
6
|
+
|
|
7
|
+
export type WSMessageType = 'text' | 'json' | 'binary' | 'ping' | 'pong';
|
|
8
|
+
|
|
9
|
+
export interface WSConfig {
|
|
10
|
+
url: string;
|
|
11
|
+
protocols?: string[];
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
/** Auto-reconnect on disconnect */
|
|
14
|
+
reconnect?: boolean;
|
|
15
|
+
/** Max reconnect attempts */
|
|
16
|
+
maxReconnectAttempts?: number;
|
|
17
|
+
/** Reconnect delay in ms */
|
|
18
|
+
reconnectDelay?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WSMessage {
|
|
22
|
+
id: string;
|
|
23
|
+
direction: 'sent' | 'received';
|
|
24
|
+
data: string;
|
|
25
|
+
type: WSMessageType;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
size: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WSFrame {
|
|
31
|
+
isFinal: boolean;
|
|
32
|
+
opcode: number;
|
|
33
|
+
masked: boolean;
|
|
34
|
+
payloadLength: number;
|
|
35
|
+
payload: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface WSConnection {
|
|
39
|
+
id: string;
|
|
40
|
+
url: string;
|
|
41
|
+
state: WSConnectionState;
|
|
42
|
+
connectedAt?: number;
|
|
43
|
+
closedAt?: number;
|
|
44
|
+
closeCode?: number;
|
|
45
|
+
closeReason?: string;
|
|
46
|
+
messageCount: number;
|
|
47
|
+
bytesReceived: number;
|
|
48
|
+
bytesSent: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WSClientEvents {
|
|
52
|
+
message: (message: WSMessage) => void;
|
|
53
|
+
open: () => void;
|
|
54
|
+
close: (code: number, reason: string) => void;
|
|
55
|
+
error: (error: Error) => void;
|
|
56
|
+
stateChange: (state: WSConnectionState) => void;
|
|
57
|
+
}
|
package/src/types/ai.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface AiTestGeneration {
|
|
2
|
+
requestId: string;
|
|
3
|
+
response: unknown;
|
|
4
|
+
prompt?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface AiEndpointDiscovery {
|
|
8
|
+
code: string;
|
|
9
|
+
framework: 'express' | 'nextjs' | 'fastapi' | 'django' | 'flask' | 'spring' | 'auto';
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AiExplanation {
|
|
14
|
+
response: unknown;
|
|
15
|
+
question?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AiSuggestion {
|
|
19
|
+
request: unknown;
|
|
20
|
+
response?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Suggestion {
|
|
24
|
+
category: 'security' | 'performance' | 'best-practice' | 'error-handling';
|
|
25
|
+
severity: 'info' | 'warning' | 'critical';
|
|
26
|
+
title: string;
|
|
27
|
+
description: string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface Collection {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
parentId?: string;
|
|
7
|
+
variables: Record<string, string>;
|
|
8
|
+
preScript?: string;
|
|
9
|
+
postScript?: string;
|
|
10
|
+
sortOrder: number;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
children?: Collection[];
|
|
14
|
+
requests?: CollectionRequest[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CollectionRequest {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
method: string;
|
|
21
|
+
url: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
sortOrder: number;
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface Environment {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
variables: EnvironmentVariable[];
|
|
6
|
+
isDefault: boolean;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface EnvironmentVariable {
|
|
12
|
+
key: string;
|
|
13
|
+
value: string;
|
|
14
|
+
type: 'string' | 'number' | 'boolean' | 'secret';
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { HttpMethod, KeyValue, BodyType, RequestBody, AuthConfig } from '@/lib/utils/validation';
|
|
2
|
+
|
|
3
|
+
export interface ApiRequest {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
collectionId: string;
|
|
8
|
+
method: HttpMethod;
|
|
9
|
+
url: string;
|
|
10
|
+
headers: KeyValue[];
|
|
11
|
+
params: KeyValue[];
|
|
12
|
+
body?: RequestBody;
|
|
13
|
+
bodyType: BodyType;
|
|
14
|
+
auth?: AuthConfig;
|
|
15
|
+
preRequestScript?: string;
|
|
16
|
+
tests?: string;
|
|
17
|
+
sortOrder: number;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CreateApiRequest {
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
collectionId: string;
|
|
26
|
+
method?: HttpMethod;
|
|
27
|
+
url: string;
|
|
28
|
+
headers?: KeyValue[];
|
|
29
|
+
params?: KeyValue[];
|
|
30
|
+
body?: RequestBody;
|
|
31
|
+
bodyType?: BodyType;
|
|
32
|
+
auth?: AuthConfig;
|
|
33
|
+
preRequestScript?: string;
|
|
34
|
+
tests?: string;
|
|
35
|
+
sortOrder?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface UpdateApiRequest extends Partial<CreateApiRequest> {
|
|
39
|
+
id: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ExecuteApiRequest {
|
|
43
|
+
method: HttpMethod;
|
|
44
|
+
url: string;
|
|
45
|
+
headers?: KeyValue[];
|
|
46
|
+
params?: KeyValue[];
|
|
47
|
+
body?: RequestBody;
|
|
48
|
+
bodyType?: BodyType;
|
|
49
|
+
auth?: AuthConfig;
|
|
50
|
+
timeout?: number;
|
|
51
|
+
followRedirects?: boolean;
|
|
52
|
+
maxRedirects?: number;
|
|
53
|
+
verifySSL?: boolean;
|
|
54
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface ApiResponse {
|
|
2
|
+
status: number;
|
|
3
|
+
statusText: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
body: string;
|
|
6
|
+
bodySize: number;
|
|
7
|
+
timing: {
|
|
8
|
+
dns: number;
|
|
9
|
+
tcp: number;
|
|
10
|
+
tls: number;
|
|
11
|
+
firstByte: number;
|
|
12
|
+
total: number;
|
|
13
|
+
};
|
|
14
|
+
url: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface HistoryEntry {
|
|
18
|
+
id: string;
|
|
19
|
+
requestId: string;
|
|
20
|
+
method: string;
|
|
21
|
+
url: string;
|
|
22
|
+
status?: number;
|
|
23
|
+
statusText?: string;
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
body?: string;
|
|
26
|
+
bodySize?: number;
|
|
27
|
+
timing?: {
|
|
28
|
+
dns: number;
|
|
29
|
+
tcp: number;
|
|
30
|
+
tls: number;
|
|
31
|
+
firstByte: number;
|
|
32
|
+
total: number;
|
|
33
|
+
};
|
|
34
|
+
testResults?: unknown;
|
|
35
|
+
duration?: number;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Config } from 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
content: [
|
|
5
|
+
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
6
|
+
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
7
|
+
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
8
|
+
],
|
|
9
|
+
darkMode: 'class',
|
|
10
|
+
theme: {
|
|
11
|
+
extend: {
|
|
12
|
+
colors: {
|
|
13
|
+
border: 'hsl(var(--border))',
|
|
14
|
+
input: 'hsl(var(--input))',
|
|
15
|
+
ring: 'hsl(var(--ring))',
|
|
16
|
+
background: 'hsl(var(--background))',
|
|
17
|
+
foreground: 'hsl(var(--foreground))',
|
|
18
|
+
primary: {
|
|
19
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
20
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
21
|
+
},
|
|
22
|
+
secondary: {
|
|
23
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
24
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
25
|
+
},
|
|
26
|
+
destructive: {
|
|
27
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
28
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
29
|
+
},
|
|
30
|
+
muted: {
|
|
31
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
32
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
33
|
+
},
|
|
34
|
+
accent: {
|
|
35
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
36
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
37
|
+
},
|
|
38
|
+
card: {
|
|
39
|
+
DEFAULT: 'hsl(var(--card))',
|
|
40
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
41
|
+
},
|
|
42
|
+
popover: {
|
|
43
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
44
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
45
|
+
},
|
|
46
|
+
method: {
|
|
47
|
+
get: '#61AFFE',
|
|
48
|
+
post: '#49CC90',
|
|
49
|
+
put: '#FCA130',
|
|
50
|
+
patch: '#50E3C2',
|
|
51
|
+
delete: '#F93E3E',
|
|
52
|
+
head: '#9012FE',
|
|
53
|
+
options: '#0D5AA7',
|
|
54
|
+
trace: '#808080',
|
|
55
|
+
connect: '#808080',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
borderRadius: {
|
|
59
|
+
lg: 'var(--radius)',
|
|
60
|
+
md: 'calc(var(--radius) - 2px)',
|
|
61
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
62
|
+
},
|
|
63
|
+
keyframes: {
|
|
64
|
+
'fade-in': {
|
|
65
|
+
from: { opacity: '0' },
|
|
66
|
+
to: { opacity: '1' },
|
|
67
|
+
},
|
|
68
|
+
'slide-in': {
|
|
69
|
+
from: { transform: 'translateX(-10px)', opacity: '0' },
|
|
70
|
+
to: { transform: 'translateX(0)', opacity: '1' },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
animation: {
|
|
74
|
+
'fade-in': 'fade-in 0.2s ease-out',
|
|
75
|
+
'slide-in': 'slide-in 0.2s ease-out',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
plugins: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default config;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
resolveVariables,
|
|
4
|
+
extractVariables,
|
|
5
|
+
hasUnresolvedVariables,
|
|
6
|
+
} from '@/lib/env/resolver';
|
|
7
|
+
|
|
8
|
+
describe('resolveVariables', () => {
|
|
9
|
+
it('should resolve a single variable', () => {
|
|
10
|
+
expect(resolveVariables('{{base_url}}', { base_url: 'https://api.example.com' }))
|
|
11
|
+
.toBe('https://api.example.com');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should resolve multiple variables', () => {
|
|
15
|
+
expect(
|
|
16
|
+
resolveVariables('{{base_url}}/users/{{id}}', {
|
|
17
|
+
base_url: 'https://api.example.com',
|
|
18
|
+
id: '123',
|
|
19
|
+
}),
|
|
20
|
+
).toBe('https://api.example.com/users/123');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should leave unresolved variables as-is', () => {
|
|
24
|
+
expect(resolveVariables('{{unknown}}/path', {})).toBe('{{unknown}}/path');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle mixed resolved and unresolved variables', () => {
|
|
28
|
+
expect(
|
|
29
|
+
resolveVariables('{{base_url}}/{{unknown}}', { base_url: 'https://api.example.com' }),
|
|
30
|
+
).toBe('https://api.example.com/{{unknown}}');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should handle string with no variables', () => {
|
|
34
|
+
expect(resolveVariables('plain string', { key: 'value' })).toBe('plain string');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should handle empty string', () => {
|
|
38
|
+
expect(resolveVariables('', { key: 'value' })).toBe('');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should resolve same variable multiple times', () => {
|
|
42
|
+
expect(resolveVariables('{{a}}-{{a}}', { a: 'X' })).toBe('X-X');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should resolve variables in header values', () => {
|
|
46
|
+
expect(
|
|
47
|
+
resolveVariables('Bearer {{token}}', { token: 'abc123' }),
|
|
48
|
+
).toBe('Bearer abc123');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should only resolve word-character variable names', () => {
|
|
52
|
+
// {{base-url}} with hyphen should NOT match \w+ pattern
|
|
53
|
+
expect(resolveVariables('{{base-url}}', { 'base-url': 'test' })).toBe('{{base-url}}');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle adjacent variables', () => {
|
|
57
|
+
expect(resolveVariables('{{a}}{{b}}', { a: 'hello', b: 'world' })).toBe('helloworld');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('extractVariables', () => {
|
|
62
|
+
it('should extract single variable', () => {
|
|
63
|
+
expect(extractVariables('{{base_url}}')).toEqual(['base_url']);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should extract multiple unique variables', () => {
|
|
67
|
+
expect(extractVariables('{{a}} and {{b}}')).toEqual(['a', 'b']);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should deduplicate variables', () => {
|
|
71
|
+
expect(extractVariables('{{a}} {{a}}')).toEqual(['a']);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return empty array for no variables', () => {
|
|
75
|
+
expect(extractVariables('no variables here')).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should return empty array for empty string', () => {
|
|
79
|
+
expect(extractVariables('')).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should extract variables in complex strings', () => {
|
|
83
|
+
const result = extractVariables('https://{{host}}/api/{{version}}/users/{{user_id}}');
|
|
84
|
+
expect(result).toEqual(['host', 'version', 'user_id']);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('hasUnresolvedVariables', () => {
|
|
89
|
+
it('should return true for string with variables', () => {
|
|
90
|
+
expect(hasUnresolvedVariables('{{base_url}}')).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return false for plain string', () => {
|
|
94
|
+
expect(hasUnresolvedVariables('https://api.example.com')).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return false for empty string', () => {
|
|
98
|
+
expect(hasUnresolvedVariables('')).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should return true for string with multiple variables', () => {
|
|
102
|
+
expect(hasUnresolvedVariables('{{a}}/{{b}}')).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return true even for single variable among text', () => {
|
|
106
|
+
expect(hasUnresolvedVariables('prefix/{{var}}/suffix')).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|