Skip to content

Commit 21358f0

Browse files
committed
feat: add mock system with UI, matching engine, and hybrid mode
- Add --mock flag for pure mock mode (no local server needed) - Auto-enable mock when --inspect is set and vice versa - Mock CRUD operations with SQLite persistence (mock-db.ts) - Request matching engine: exact, prefix, regex with priority (mock-matcher.ts) - Serve mock responses via ResponseChannel with delay support (mock-handler.ts) - Mock management UI at /mocks on inspector server - Mock API routes (GET/POST/PUT/DELETE/PATCH /api/mocks) - MOCK pill badge in inspector list view, banner in detail view - Hybrid mode: mocked paths return mock data, others proxy normally - Make --port optional (required only without --mock)
1 parent 130b6b0 commit 21358f0

8 files changed

Lines changed: 1011 additions & 78 deletions

File tree

packages/client/src/@types/index.d.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
declare global {
22
interface ClientInitializationOptions {
3-
port: number;
3+
port?: number;
44
debug?: boolean;
55
token?: string;
66
inspect?: boolean;
77
inspectPort?: number;
8+
mock?: boolean;
89
}
910

1011
interface TunnelRequestArgument {
@@ -88,6 +89,47 @@ declare global {
8889
avg_duration_ms: number | null;
8990
}
9091

92+
interface MockDefinition {
93+
id: string;
94+
method: string;
95+
path: string;
96+
path_type: 'exact' | 'prefix' | 'regex';
97+
status_code: number;
98+
headers: string;
99+
body: string | null;
100+
delay_ms: number;
101+
priority: number;
102+
enabled: number;
103+
description: string | null;
104+
created_at: string;
105+
updated_at: string;
106+
}
107+
108+
interface MockInsert {
109+
method?: string;
110+
path: string;
111+
path_type?: string;
112+
status_code?: number;
113+
headers?: string;
114+
body?: string;
115+
delay_ms?: number;
116+
priority?: number;
117+
description?: string;
118+
}
119+
120+
interface MockUpdate {
121+
method?: string;
122+
path?: string;
123+
path_type?: string;
124+
status_code?: number;
125+
headers?: string;
126+
body?: string;
127+
delay_ms?: number;
128+
priority?: number;
129+
enabled?: number;
130+
description?: string;
131+
}
132+
91133
}
92134

93135
export {};

packages/client/src/index.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,51 @@ program
2323
.name('ProxyHub')
2424
.description("Test your API's with ease - Tunnel localhost to the internet")
2525
.version(version)
26-
.requiredOption('-p, --port <port>', 'Port number for proxying', portNumberCustomValidationForCommander)
26+
.option('-p, --port <port>', 'Port number for proxying', portNumberCustomValidationForCommander)
2727
.option('-d, --debug', 'Enable debug mode', false)
2828
.option('-t, --token <token>', 'Token for tunnel protection')
2929
.option('-i, --inspect', 'Enable request inspector', false)
30+
.option('-m, --mock', 'Enable mock mode', false)
3031
.option('--inspect-port <port>', 'Port for inspector UI', parseInt);
3132

3233
// Parse command line arguments
3334
program.parse(process.argv);
3435

3536
// Get parsed options and check for env var fallback
36-
const parsedOpts = program.opts() as ClientInitializationOptions;
37+
const parsedOpts = program.opts() as ClientInitializationOptions & { port?: number };
3738
const options: ClientInitializationOptions = {
3839
port: parsedOpts.port,
3940
debug: parsedOpts.debug,
4041
token: parsedOpts.token || process.env.PROXYHUB_TOKEN,
4142
inspect: parsedOpts.inspect,
4243
inspectPort: parsedOpts.inspectPort,
44+
mock: parsedOpts.mock,
4345
};
4446

47+
// Validate: need either port or mock mode
48+
if (!options.port && !options.mock) {
49+
console.error(chalk.red('Error: Either --port or --mock is required.'));
50+
console.error(chalk.gray(' Use --port <port> to proxy to a local server'));
51+
console.error(chalk.gray(' Use --mock for pure mock mode (no local server needed)'));
52+
process.exit(1);
53+
}
54+
55+
// Mock and inspect are co-dependent — enabling either enables both
56+
if (options.mock || options.inspect) {
57+
options.mock = true;
58+
options.inspect = true;
59+
}
60+
4561
// Startup logging
4662
console.log('\nStarting ProxyHub Client...');
47-
console.log('Target:', chalk.cyan(`http://localhost:${options.port}`));
63+
if (options.port) {
64+
console.log('Target:', chalk.cyan(`http://localhost:${options.port}`));
65+
}
66+
if (options.mock && options.port) {
67+
console.log('Mode:', chalk.magenta('hybrid (mock + proxy)'));
68+
} else if (options.mock) {
69+
console.log('Mode:', chalk.magenta('pure mock (no local server)'));
70+
}
4871
if (options.token) {
4972
console.log('Token protection:', chalk.green('enabled'));
5073
}
@@ -61,7 +84,6 @@ socketHandler(options);
6184

6285
// Start inspector if enabled
6386
if (options.inspect) {
64-
const inspectPort = options.inspectPort || options.port + 1000;
65-
startInspector(inspectPort, options.port);
87+
const inspectPort = options.inspectPort || (options.port ? options.port + 1000 : 3001);
88+
startInspector(inspectPort, options.port, { mock: options.mock });
6689
}
67-

packages/client/src/lib/db.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ export function initDb(): void {
4040
CREATE INDEX IF NOT EXISTS idx_requests_method ON requests(method);
4141
CREATE INDEX IF NOT EXISTS idx_requests_status_code ON requests(status_code);
4242
CREATE INDEX IF NOT EXISTS idx_requests_path ON requests(path);
43+
44+
CREATE TABLE IF NOT EXISTS mocks (
45+
id TEXT PRIMARY KEY,
46+
method TEXT NOT NULL DEFAULT '*',
47+
path TEXT NOT NULL,
48+
path_type TEXT NOT NULL DEFAULT 'exact',
49+
status_code INTEGER NOT NULL DEFAULT 200,
50+
headers TEXT NOT NULL DEFAULT '{}',
51+
body TEXT,
52+
delay_ms INTEGER NOT NULL DEFAULT 0,
53+
priority INTEGER NOT NULL DEFAULT 0,
54+
enabled INTEGER NOT NULL DEFAULT 1,
55+
description TEXT,
56+
created_at TEXT NOT NULL,
57+
updated_at TEXT NOT NULL
58+
);
59+
CREATE INDEX IF NOT EXISTS idx_mocks_enabled ON mocks(enabled);
4360
`);
4461

4562
// Migrate: add response_body column if missing (existing DBs)
@@ -57,7 +74,7 @@ export function closeDb(): void {
5774
}
5875
}
5976

60-
function getDb(): Database.Database {
77+
export function getDb(): Database.Database {
6178
if (!db) throw new Error('Database not initialized. Call initDb() first.');
6279
return db;
6380
}

0 commit comments

Comments
 (0)