@zze/mock-server 0.2.6 → 0.5.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/README.md +117 -28
- package/dist/__tests__/mock-server.test.js +52 -0
- package/dist/admin/__tests__/admin-router.test.js +0 -151
- package/dist/admin/admin-router.d.ts.map +1 -1
- package/dist/admin/admin-router.js +0 -205
- package/dist/admin/types.d.ts +0 -84
- package/dist/admin/types.d.ts.map +1 -1
- package/dist/admin/types.js +1 -5
- package/dist/config/config-writer.d.ts +1 -35
- package/dist/config/config-writer.d.ts.map +1 -1
- package/dist/config/config-writer.js +0 -103
- package/dist/mock-server.d.ts +9 -0
- package/dist/mock-server.d.ts.map +1 -1
- package/dist/mock-server.js +3 -0
- package/dist/resolver/__tests__/response-resolver.test.js +134 -0
- package/dist/resolver/response-resolver.d.ts +18 -2
- package/dist/resolver/response-resolver.d.ts.map +1 -1
- package/dist/resolver/response-resolver.js +62 -5
- package/dist/resolver/types.d.ts +8 -0
- package/dist/resolver/types.d.ts.map +1 -1
- package/dist/resolver/types.js +7 -0
- package/dist/schemas/__tests__/endpoint.schema.test.js +48 -0
- package/dist/schemas/endpoint.schema.d.ts +31 -8
- package/dist/schemas/endpoint.schema.d.ts.map +1 -1
- package/dist/schemas/scenario.schema.d.ts +28 -4
- package/dist/schemas/scenario.schema.d.ts.map +1 -1
- package/dist/schemas/scenario.schema.js +23 -3
- package/dist/server/__tests__/mock-server-app.test.js +37 -0
- package/dist/server/mock-server-app.d.ts.map +1 -1
- package/dist/server/mock-server-app.js +5 -1
- package/dist/server/types.d.ts +8 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +1 -0
- package/dist/ui/assets/{index-rj7m-XdG.css → index-C5BZSa2z.css} +1 -1
- package/dist/ui/assets/index-DY4CS66u.js +9 -0
- package/dist/ui/index.html +2 -2
- package/package.json +6 -4
- package/dist/ui/assets/index-SADMOgPE.js +0 -10
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.ResponseResolver = void 0;
|
|
7
|
+
exports.resolveDelay = resolveDelay;
|
|
4
8
|
exports.createResponseResolver = createResponseResolver;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
5
10
|
const types_js_1 = require("./types.js");
|
|
6
11
|
/**
|
|
7
12
|
* Resolves which response to send for incoming requests
|
|
8
13
|
* Uses StateManager to determine active scenario based on priority chain
|
|
9
14
|
*/
|
|
10
15
|
class ResponseResolver {
|
|
11
|
-
constructor(stateManager) {
|
|
16
|
+
constructor(stateManager, configPath = '', globalDelay) {
|
|
12
17
|
this.endpoints = new Map();
|
|
13
18
|
this.routeMap = new Map();
|
|
14
19
|
this.stateManager = stateManager;
|
|
20
|
+
this.configPath = configPath;
|
|
21
|
+
this.globalDelay = globalDelay;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Update the config path (used when config is reloaded from a new location)
|
|
25
|
+
*/
|
|
26
|
+
setConfigPath(configPath) {
|
|
27
|
+
this.configPath = configPath;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Update the global delay at runtime
|
|
31
|
+
*/
|
|
32
|
+
setGlobalDelay(globalDelay) {
|
|
33
|
+
this.globalDelay = globalDelay;
|
|
15
34
|
}
|
|
16
35
|
/**
|
|
17
36
|
* Register endpoints for routing
|
|
@@ -80,14 +99,18 @@ class ResponseResolver {
|
|
|
80
99
|
* Build the resolved response from endpoint and scenario
|
|
81
100
|
*/
|
|
82
101
|
buildResponse(endpoint, scenario, req) {
|
|
83
|
-
|
|
102
|
+
const resolved = {
|
|
84
103
|
status: scenario.status,
|
|
85
104
|
headers: scenario.headers ?? {},
|
|
86
105
|
body: scenario.body,
|
|
87
|
-
delay: scenario.delay
|
|
106
|
+
delay: resolveDelay(scenario.delay, this.globalDelay),
|
|
88
107
|
scenario,
|
|
89
108
|
endpoint,
|
|
90
109
|
};
|
|
110
|
+
if (scenario.file) {
|
|
111
|
+
resolved.filePath = path_1.default.resolve(this.configPath, scenario.file);
|
|
112
|
+
}
|
|
113
|
+
return resolved;
|
|
91
114
|
}
|
|
92
115
|
/**
|
|
93
116
|
* Create request context for dynamic body functions
|
|
@@ -130,6 +153,24 @@ class ResponseResolver {
|
|
|
130
153
|
for (const [key, value] of Object.entries(resolved.headers)) {
|
|
131
154
|
res.setHeader(key, value);
|
|
132
155
|
}
|
|
156
|
+
// File download response
|
|
157
|
+
if ((0, types_js_1.isFileResponse)(resolved)) {
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
res.status(resolved.status).sendFile(resolved.filePath, (err) => {
|
|
160
|
+
if (err) {
|
|
161
|
+
// File not found or send error
|
|
162
|
+
if (!res.headersSent) {
|
|
163
|
+
res.status(404).json({ error: `File not found: ${resolved.filePath}` });
|
|
164
|
+
}
|
|
165
|
+
resolve();
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
resolve();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
133
174
|
// Evaluate body (handle dynamic functions)
|
|
134
175
|
const context = this.createRequestContext(req);
|
|
135
176
|
const body = await this.evaluateBody(resolved.body, context);
|
|
@@ -159,9 +200,25 @@ class ResponseResolver {
|
|
|
159
200
|
}
|
|
160
201
|
}
|
|
161
202
|
exports.ResponseResolver = ResponseResolver;
|
|
203
|
+
/**
|
|
204
|
+
* Resolve the effective delay for a response.
|
|
205
|
+
* The scenario's own delay (when defined) always wins over the global fallback.
|
|
206
|
+
* A globalDelay tuple [min, max] produces a uniformly distributed random value.
|
|
207
|
+
*/
|
|
208
|
+
function resolveDelay(scenarioDelay, globalDelay) {
|
|
209
|
+
if (scenarioDelay !== undefined)
|
|
210
|
+
return scenarioDelay;
|
|
211
|
+
if (globalDelay === undefined)
|
|
212
|
+
return 0;
|
|
213
|
+
if (Array.isArray(globalDelay)) {
|
|
214
|
+
const [min, max] = globalDelay;
|
|
215
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
216
|
+
}
|
|
217
|
+
return globalDelay;
|
|
218
|
+
}
|
|
162
219
|
/**
|
|
163
220
|
* Create a new ResponseResolver instance
|
|
164
221
|
*/
|
|
165
|
-
function createResponseResolver(stateManager) {
|
|
166
|
-
return new ResponseResolver(stateManager);
|
|
222
|
+
function createResponseResolver(stateManager, configPath, globalDelay) {
|
|
223
|
+
return new ResponseResolver(stateManager, configPath, globalDelay);
|
|
167
224
|
}
|
package/dist/resolver/types.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface ResolvedResponse {
|
|
|
10
10
|
headers: Record<string, string>;
|
|
11
11
|
/** Response body */
|
|
12
12
|
body: unknown;
|
|
13
|
+
/** Absolute path to file for file download responses */
|
|
14
|
+
filePath?: string;
|
|
13
15
|
/** Delay in milliseconds before sending response */
|
|
14
16
|
delay: number;
|
|
15
17
|
/** The scenario that was resolved */
|
|
@@ -17,6 +19,12 @@ export interface ResolvedResponse {
|
|
|
17
19
|
/** The endpoint that was matched */
|
|
18
20
|
endpoint: EndpointConfig;
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if a resolved response is a file download
|
|
24
|
+
*/
|
|
25
|
+
export declare function isFileResponse(resolved: ResolvedResponse): resolved is ResolvedResponse & {
|
|
26
|
+
filePath: string;
|
|
27
|
+
};
|
|
20
28
|
/**
|
|
21
29
|
* Context passed to dynamic body functions
|
|
22
30
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/resolver/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,oBAAoB;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,QAAQ,EAAE,cAAc,CAAC;IACzB,oCAAoC;IACpC,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mBAAmB;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,aAAa,CAElE"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/resolver/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,oBAAoB;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,QAAQ,EAAE,cAAc,CAAC;IACzB,oCAAoC;IACpC,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,QAAQ,IAAI,gBAAgB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAE9G;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mBAAmB;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,aAAa,CAElE"}
|
package/dist/resolver/types.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isFileResponse = isFileResponse;
|
|
3
4
|
exports.isDynamicBody = isDynamicBody;
|
|
5
|
+
/**
|
|
6
|
+
* Check if a resolved response is a file download
|
|
7
|
+
*/
|
|
8
|
+
function isFileResponse(resolved) {
|
|
9
|
+
return resolved.filePath !== undefined;
|
|
10
|
+
}
|
|
4
11
|
/**
|
|
5
12
|
* Check if a value is a dynamic body function
|
|
6
13
|
*/
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const vitest_1 = require("vitest");
|
|
4
4
|
const endpoint_schema_js_1 = require("../endpoint.schema.js");
|
|
5
|
+
const scenario_schema_js_1 = require("../scenario.schema.js");
|
|
5
6
|
(0, vitest_1.describe)('EndpointConfigSchema', () => {
|
|
6
7
|
const validEndpoint = {
|
|
7
8
|
id: 'get-user-data',
|
|
@@ -101,3 +102,50 @@ const endpoint_schema_js_1 = require("../endpoint.schema.js");
|
|
|
101
102
|
(0, vitest_1.expect)(result.duplicates).toContain('users');
|
|
102
103
|
});
|
|
103
104
|
});
|
|
105
|
+
(0, vitest_1.describe)('ScenarioConfigSchema - file field', () => {
|
|
106
|
+
const baseScenario = {
|
|
107
|
+
id: 'download',
|
|
108
|
+
name: 'Download',
|
|
109
|
+
status: 200,
|
|
110
|
+
};
|
|
111
|
+
(0, vitest_1.it)('should accept a scenario with only a file field', () => {
|
|
112
|
+
const result = scenario_schema_js_1.ScenarioConfigSchema.safeParse({
|
|
113
|
+
...baseScenario,
|
|
114
|
+
file: './assets/report.pdf',
|
|
115
|
+
});
|
|
116
|
+
(0, vitest_1.expect)(result.success).toBe(true);
|
|
117
|
+
if (result.success) {
|
|
118
|
+
(0, vitest_1.expect)(result.data.file).toBe('./assets/report.pdf');
|
|
119
|
+
(0, vitest_1.expect)(result.data.body).toBeUndefined();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
(0, vitest_1.it)('should accept a scenario with only a body field', () => {
|
|
123
|
+
const result = scenario_schema_js_1.ScenarioConfigSchema.safeParse({
|
|
124
|
+
...baseScenario,
|
|
125
|
+
body: { ok: true },
|
|
126
|
+
});
|
|
127
|
+
(0, vitest_1.expect)(result.success).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
(0, vitest_1.it)('should accept a scenario with neither body nor file', () => {
|
|
130
|
+
const result = scenario_schema_js_1.ScenarioConfigSchema.safeParse(baseScenario);
|
|
131
|
+
(0, vitest_1.expect)(result.success).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
(0, vitest_1.it)('should reject a scenario with both body and file', () => {
|
|
134
|
+
const result = scenario_schema_js_1.ScenarioConfigSchema.safeParse({
|
|
135
|
+
...baseScenario,
|
|
136
|
+
body: { data: 'some json' },
|
|
137
|
+
file: './assets/report.pdf',
|
|
138
|
+
});
|
|
139
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
140
|
+
if (!result.success) {
|
|
141
|
+
(0, vitest_1.expect)(result.error.issues[0].message).toContain("cannot specify both 'body' and 'file'");
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
(0, vitest_1.it)('should reject an empty file path', () => {
|
|
145
|
+
const result = scenario_schema_js_1.ScenarioConfigSchema.safeParse({
|
|
146
|
+
...baseScenario,
|
|
147
|
+
file: '',
|
|
148
|
+
});
|
|
149
|
+
(0, vitest_1.expect)(result.success).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -11,26 +11,45 @@ export declare const EndpointConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
11
11
|
id: z.ZodString;
|
|
12
12
|
path: z.ZodString;
|
|
13
13
|
method: z.ZodEnum<["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]>;
|
|
14
|
-
scenarios: z.ZodArray<z.ZodObject<{
|
|
14
|
+
scenarios: z.ZodArray<z.ZodEffects<z.ZodObject<{
|
|
15
15
|
id: z.ZodString;
|
|
16
16
|
name: z.ZodString;
|
|
17
17
|
status: z.ZodNumber;
|
|
18
18
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
19
|
-
body: z.ZodUnion<[z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodArray<z.ZodUnknown, "many">, z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodFunction<z.ZodTuple<[z.ZodAny], z.ZodUnknown>, z.ZodAny>]
|
|
19
|
+
body: z.ZodOptional<z.ZodUnion<[z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodArray<z.ZodUnknown, "many">, z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodFunction<z.ZodTuple<[z.ZodAny], z.ZodUnknown>, z.ZodAny>]>>;
|
|
20
|
+
file: z.ZodOptional<z.ZodString>;
|
|
20
21
|
delay: z.ZodOptional<z.ZodNumber>;
|
|
21
22
|
}, "strip", z.ZodTypeAny, {
|
|
22
23
|
status: number;
|
|
23
24
|
id: string;
|
|
24
25
|
name: string;
|
|
25
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
26
26
|
headers?: Record<string, string> | undefined;
|
|
27
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
28
|
+
file?: string | undefined;
|
|
27
29
|
delay?: number | undefined;
|
|
28
30
|
}, {
|
|
29
31
|
status: number;
|
|
30
32
|
id: string;
|
|
31
33
|
name: string;
|
|
32
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
33
34
|
headers?: Record<string, string> | undefined;
|
|
35
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
36
|
+
file?: string | undefined;
|
|
37
|
+
delay?: number | undefined;
|
|
38
|
+
}>, {
|
|
39
|
+
status: number;
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
headers?: Record<string, string> | undefined;
|
|
43
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
44
|
+
file?: string | undefined;
|
|
45
|
+
delay?: number | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
status: number;
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
headers?: Record<string, string> | undefined;
|
|
51
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
52
|
+
file?: string | undefined;
|
|
34
53
|
delay?: number | undefined;
|
|
35
54
|
}>, "many">;
|
|
36
55
|
defaultScenarioId: z.ZodString;
|
|
@@ -42,8 +61,9 @@ export declare const EndpointConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
42
61
|
status: number;
|
|
43
62
|
id: string;
|
|
44
63
|
name: string;
|
|
45
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
46
64
|
headers?: Record<string, string> | undefined;
|
|
65
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
66
|
+
file?: string | undefined;
|
|
47
67
|
delay?: number | undefined;
|
|
48
68
|
}[];
|
|
49
69
|
defaultScenarioId: string;
|
|
@@ -55,8 +75,9 @@ export declare const EndpointConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
55
75
|
status: number;
|
|
56
76
|
id: string;
|
|
57
77
|
name: string;
|
|
58
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
59
78
|
headers?: Record<string, string> | undefined;
|
|
79
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
80
|
+
file?: string | undefined;
|
|
60
81
|
delay?: number | undefined;
|
|
61
82
|
}[];
|
|
62
83
|
defaultScenarioId: string;
|
|
@@ -68,8 +89,9 @@ export declare const EndpointConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
68
89
|
status: number;
|
|
69
90
|
id: string;
|
|
70
91
|
name: string;
|
|
71
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
72
92
|
headers?: Record<string, string> | undefined;
|
|
93
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
94
|
+
file?: string | undefined;
|
|
73
95
|
delay?: number | undefined;
|
|
74
96
|
}[];
|
|
75
97
|
defaultScenarioId: string;
|
|
@@ -81,8 +103,9 @@ export declare const EndpointConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
81
103
|
status: number;
|
|
82
104
|
id: string;
|
|
83
105
|
name: string;
|
|
84
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
85
106
|
headers?: Record<string, string> | undefined;
|
|
107
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
108
|
+
file?: string | undefined;
|
|
86
109
|
delay?: number | undefined;
|
|
87
110
|
}[];
|
|
88
111
|
defaultScenarioId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/endpoint.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB;;GAEG;AACH,eAAO,MAAM,UAAU,aAKuC,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,oBAAoB
|
|
1
|
+
{"version":3,"file":"endpoint.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/endpoint.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB;;GAEG;AACH,eAAO,MAAM,UAAU,aAKuC,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8B7B,CAAC;AAEL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,cAAc,EAAE,GAC1B;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAgBxE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,cAAc,EAAE,GAC1B;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAgB1C"}
|
|
@@ -17,30 +17,54 @@ export type ResponseBody = z.infer<typeof ResponseBodySchema>;
|
|
|
17
17
|
* Response delay in milliseconds (0-30000ms max to prevent hanging)
|
|
18
18
|
*/
|
|
19
19
|
export declare const DelaySchema: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
/**
|
|
21
|
+
* File path for file download responses
|
|
22
|
+
* Relative to the config directory
|
|
23
|
+
*/
|
|
24
|
+
export declare const FilePathSchema: z.ZodString;
|
|
20
25
|
/**
|
|
21
26
|
* Scenario configuration schema
|
|
22
27
|
* Defines a single response scenario for an endpoint
|
|
23
28
|
*/
|
|
24
|
-
export declare const ScenarioConfigSchema: z.ZodObject<{
|
|
29
|
+
export declare const ScenarioConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
25
30
|
id: z.ZodString;
|
|
26
31
|
name: z.ZodString;
|
|
27
32
|
status: z.ZodNumber;
|
|
28
33
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
29
|
-
body: z.ZodUnion<[z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodArray<z.ZodUnknown, "many">, z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodFunction<z.ZodTuple<[z.ZodAny], z.ZodUnknown>, z.ZodAny>]
|
|
34
|
+
body: z.ZodOptional<z.ZodUnion<[z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodArray<z.ZodUnknown, "many">, z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull, z.ZodFunction<z.ZodTuple<[z.ZodAny], z.ZodUnknown>, z.ZodAny>]>>;
|
|
35
|
+
file: z.ZodOptional<z.ZodString>;
|
|
30
36
|
delay: z.ZodOptional<z.ZodNumber>;
|
|
31
37
|
}, "strip", z.ZodTypeAny, {
|
|
32
38
|
status: number;
|
|
33
39
|
id: string;
|
|
34
40
|
name: string;
|
|
35
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
36
41
|
headers?: Record<string, string> | undefined;
|
|
42
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
43
|
+
file?: string | undefined;
|
|
44
|
+
delay?: number | undefined;
|
|
45
|
+
}, {
|
|
46
|
+
status: number;
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
headers?: Record<string, string> | undefined;
|
|
50
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
51
|
+
file?: string | undefined;
|
|
52
|
+
delay?: number | undefined;
|
|
53
|
+
}>, {
|
|
54
|
+
status: number;
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
headers?: Record<string, string> | undefined;
|
|
58
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
59
|
+
file?: string | undefined;
|
|
37
60
|
delay?: number | undefined;
|
|
38
61
|
}, {
|
|
39
62
|
status: number;
|
|
40
63
|
id: string;
|
|
41
64
|
name: string;
|
|
42
|
-
body: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null;
|
|
43
65
|
headers?: Record<string, string> | undefined;
|
|
66
|
+
body?: string | number | boolean | unknown[] | Record<string, unknown> | ((args_0: any, ...args: unknown[]) => any) | null | undefined;
|
|
67
|
+
file?: string | undefined;
|
|
44
68
|
delay?: number | undefined;
|
|
45
69
|
}>;
|
|
46
70
|
export type ScenarioConfig = z.infer<typeof ScenarioConfigSchema>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/scenario.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,eAAO,MAAM,QAAQ,aAOlB,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,UAAU,aAIW,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,0MAQ7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,WAAW,4BAMqB,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,oBAAoB
|
|
1
|
+
{"version":3,"file":"scenario.schema.d.ts","sourceRoot":"","sources":["../../src/schemas/scenario.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,eAAO,MAAM,QAAQ,aAOlB,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,UAAU,aAIW,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,0MAQ7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,WAAW,4BAMqB,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,cAAc,aAI4D,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkB7B,CAAC;AAEL,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,cAAc,EAAE,GAC1B;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAgB1C"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ScenarioConfigSchema = exports.DelaySchema = exports.ResponseBodySchema = exports.NameSchema = exports.IdSchema = void 0;
|
|
3
|
+
exports.ScenarioConfigSchema = exports.FilePathSchema = exports.DelaySchema = exports.ResponseBodySchema = exports.NameSchema = exports.IdSchema = void 0;
|
|
4
4
|
exports.validateUniqueScenarioIds = validateUniqueScenarioIds;
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const http_schema_js_1 = require("./http.schema.js");
|
|
@@ -43,17 +43,37 @@ exports.DelaySchema = zod_1.z
|
|
|
43
43
|
.max(30000)
|
|
44
44
|
.optional()
|
|
45
45
|
.describe('Response delay in milliseconds');
|
|
46
|
+
/**
|
|
47
|
+
* File path for file download responses
|
|
48
|
+
* Relative to the config directory
|
|
49
|
+
*/
|
|
50
|
+
exports.FilePathSchema = zod_1.z
|
|
51
|
+
.string()
|
|
52
|
+
.min(1)
|
|
53
|
+
.max(1000)
|
|
54
|
+
.describe('File path relative to the config directory (e.g., "./assets/report.pdf")');
|
|
46
55
|
/**
|
|
47
56
|
* Scenario configuration schema
|
|
48
57
|
* Defines a single response scenario for an endpoint
|
|
49
58
|
*/
|
|
50
|
-
exports.ScenarioConfigSchema = zod_1.z
|
|
59
|
+
exports.ScenarioConfigSchema = zod_1.z
|
|
60
|
+
.object({
|
|
51
61
|
id: exports.IdSchema.describe('Unique scenario identifier'),
|
|
52
62
|
name: exports.NameSchema.describe('Human-readable scenario name'),
|
|
53
63
|
status: http_schema_js_1.HttpStatusCodeSchema,
|
|
54
64
|
headers: http_schema_js_1.HttpHeadersSchema,
|
|
55
|
-
body: exports.ResponseBodySchema.describe('Response body'),
|
|
65
|
+
body: exports.ResponseBodySchema.describe('Response body').optional(),
|
|
66
|
+
file: exports.FilePathSchema.optional(),
|
|
56
67
|
delay: exports.DelaySchema,
|
|
68
|
+
})
|
|
69
|
+
.superRefine((data, ctx) => {
|
|
70
|
+
if (data.body !== undefined && data.file !== undefined) {
|
|
71
|
+
ctx.addIssue({
|
|
72
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
73
|
+
message: `Scenario "${data.id}": cannot specify both 'body' and 'file' — use one or the other`,
|
|
74
|
+
path: ['file'],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
57
77
|
});
|
|
58
78
|
/**
|
|
59
79
|
* Validates that scenario IDs are unique within an array
|
|
@@ -294,3 +294,40 @@ const mockFlows = [
|
|
|
294
294
|
(0, vitest_1.expect)(app.getOptions().port).toBe(5000);
|
|
295
295
|
});
|
|
296
296
|
});
|
|
297
|
+
(0, vitest_1.describe)('multipart upload support', () => {
|
|
298
|
+
let serverApp;
|
|
299
|
+
const uploadEndpoint = {
|
|
300
|
+
id: 'upload-avatar',
|
|
301
|
+
path: '/users/:id/avatar',
|
|
302
|
+
method: 'POST',
|
|
303
|
+
scenarios: [
|
|
304
|
+
{ id: 'success', name: 'Uploaded', status: 200, body: { url: '/cdn/avatar.jpg' } },
|
|
305
|
+
{ id: 'too-large', name: 'Too Large', status: 413, body: { error: 'File too large' } },
|
|
306
|
+
],
|
|
307
|
+
defaultScenarioId: 'success',
|
|
308
|
+
};
|
|
309
|
+
(0, vitest_1.beforeEach)(() => {
|
|
310
|
+
serverApp = new mock_server_app_js_1.MockServerApp({ port: 0 });
|
|
311
|
+
serverApp.registerConfig([uploadEndpoint], []);
|
|
312
|
+
});
|
|
313
|
+
(0, vitest_1.afterEach)(async () => {
|
|
314
|
+
if (serverApp.isRunning()) {
|
|
315
|
+
await serverApp.stop();
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
(0, vitest_1.it)('should accept multipart/form-data and return active scenario', async () => {
|
|
319
|
+
const res = await (0, supertest_1.default)(serverApp.getApp())
|
|
320
|
+
.post('/api/users/123/avatar')
|
|
321
|
+
.attach('file', Buffer.from('fake image data'), { filename: 'avatar.png', contentType: 'image/png' })
|
|
322
|
+
.expect(200);
|
|
323
|
+
(0, vitest_1.expect)(res.body).toEqual({ url: '/cdn/avatar.jpg' });
|
|
324
|
+
});
|
|
325
|
+
(0, vitest_1.it)('should return the active scenario regardless of uploaded file', async () => {
|
|
326
|
+
serverApp.getStateManager().activateScenario('upload-avatar', 'too-large');
|
|
327
|
+
const res = await (0, supertest_1.default)(serverApp.getApp())
|
|
328
|
+
.post('/api/users/123/avatar')
|
|
329
|
+
.attach('file', Buffer.from('big file'), { filename: 'big.png', contentType: 'image/png' })
|
|
330
|
+
.expect(413);
|
|
331
|
+
(0, vitest_1.expect)(res.body).toEqual({ error: 'File too large' });
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-server-app.d.ts","sourceRoot":"","sources":["../../src/server/mock-server-app.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,OAAO,EAA2C,MAAM,SAAS,CAAC;AACpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"mock-server-app.d.ts","sourceRoot":"","sources":["../../src/server/mock-server-app.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,OAAO,EAA2C,MAAM,SAAS,CAAC;AACpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAsB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAA0B,MAAM,sBAAsB,CAAC;AAEhF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAI9E,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAE3C;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,cAAc,CAAC,CAAsB;gBAEjC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM;IAahD;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAiCpB;;OAEG;IACH,OAAO,CAAC,eAAe;IAIvB;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,eAAe,IAAI,YAAY;IAI/B;;OAEG;IACH,WAAW,IAAI,gBAAgB;IAI/B;;OAEG;IACH,UAAU,IAAI,aAAa;IAI3B;;OAEG;IACH,mBAAmB,IAAI,eAAe,EAAE;IAIxC;;OAEG;IACH,eAAe,CAAC,YAAY,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAKvF;;OAEG;IACH,QAAQ,IAAI,WAAW;IASvB;;OAEG;IACH,cAAc,CAAC,SAAS,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;IAgBtE;;OAEG;IACH,OAAO,CAAC,cAAc;IAmBtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAwBxB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgB7B;;OAEG;IACH,GAAG,CACD,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAClC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAI7B;;OAEG;IACG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAyB3C;;OAEG;IACH,iBAAiB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAexE;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,aAAa,IAAI,MAAM,GAAG,IAAI;IAI9B;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;CAKrE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GACnC,aAAa,CAEf"}
|
|
@@ -7,6 +7,7 @@ exports.MockServerApp = exports.ADMIN_UI_PATH = void 0;
|
|
|
7
7
|
exports.createMockServerApp = createMockServerApp;
|
|
8
8
|
const express_1 = __importDefault(require("express"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const multer_1 = __importDefault(require("multer"));
|
|
10
11
|
const index_js_1 = require("../state/index.js");
|
|
11
12
|
const index_js_2 = require("../resolver/index.js");
|
|
12
13
|
const index_js_3 = require("../admin/index.js");
|
|
@@ -25,7 +26,7 @@ class MockServerApp {
|
|
|
25
26
|
this.options = { ...types_js_1.DEFAULT_SERVER_OPTIONS, ...options };
|
|
26
27
|
this.app = (0, express_1.default)();
|
|
27
28
|
this.stateManager = (0, index_js_1.createStateManager)();
|
|
28
|
-
this.resolver = (0, index_js_2.createResponseResolver)(this.stateManager);
|
|
29
|
+
this.resolver = (0, index_js_2.createResponseResolver)(this.stateManager, this.options.configPath, this.options.globalDelay);
|
|
29
30
|
this.mockRouter = express_1.default.Router();
|
|
30
31
|
this.setupMiddleware();
|
|
31
32
|
this.mountAdminRouter();
|
|
@@ -40,6 +41,8 @@ class MockServerApp {
|
|
|
40
41
|
this.app.use(express_1.default.json());
|
|
41
42
|
// Parse URL-encoded bodies
|
|
42
43
|
this.app.use(express_1.default.urlencoded({ extended: true }));
|
|
44
|
+
// Parse multipart/form-data (file uploads) — files are discarded, fields are parsed
|
|
45
|
+
this.app.use((0, multer_1.default)({ storage: multer_1.default.memoryStorage() }).any());
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
45
48
|
* Mount the admin API router
|
|
@@ -153,6 +156,7 @@ class MockServerApp {
|
|
|
153
156
|
this.stateManager.setFlows(flows);
|
|
154
157
|
// Update resolver
|
|
155
158
|
this.resolver.setEndpoints(endpoints);
|
|
159
|
+
this.resolver.setConfigPath(this.options.configPath);
|
|
156
160
|
// Register routes
|
|
157
161
|
this.registerRoutes(endpoints);
|
|
158
162
|
}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ export interface ServerOptions {
|
|
|
8
8
|
apiPrefix: string;
|
|
9
9
|
/** Enable hot reload (default: true) */
|
|
10
10
|
hotReload: boolean;
|
|
11
|
+
/** Base directory for resolving relative file paths in scenarios (default: '') */
|
|
12
|
+
configPath: string;
|
|
13
|
+
/**
|
|
14
|
+
* Global delay applied to every response when the scenario has no explicit `delay`.
|
|
15
|
+
* A number is a fixed ms value; a tuple [min, max] produces a random ms value in that range.
|
|
16
|
+
* Scenario-level delay always takes precedence.
|
|
17
|
+
*/
|
|
18
|
+
globalDelay?: number | [number, number];
|
|
11
19
|
}
|
|
12
20
|
/**
|
|
13
21
|
* Default server options
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAC;IACnB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAKpC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,qCAAqC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
package/dist/server/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--primary: #4f46e5;--primary-hover: #4338ca;--success: #10b981;--warning: #f59e0b;--error: #ef4444;--bg: #f5f5f5;--card-bg: #ffffff;--border: #e5e7eb;--text: #1f2937;--text-muted: #6b7280}*{box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background:var(--bg);color:var(--text);margin:0;padding:0}.app{max-width:1200px;margin:0 auto;padding:20px}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}.header h1{font-size:24px;font-weight:600;color:var(--text)}.header-actions{display:flex;gap:12px}.btn{padding:8px 16px;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:all .15s ease}.btn-primary{background:var(--primary);color:#fff}.btn-primary:hover{background:var(--primary-hover)}.btn-secondary{background:var(--card-bg);color:var(--text);border:1px solid var(--border)}.btn-secondary:hover{background:var(--bg)}.btn-danger{background:var(--error);color:#fff}.btn-danger:hover{background:#dc2626}.btn-sm{padding:4px 10px;font-size:12px}.grid{display:grid;grid-template-columns:1fr 300px;gap:24px}.main-content{display:flex;flex-direction:column;gap:24px}.card{background:var(--card-bg);border-radius:8px;border:1px solid var(--border);overflow:hidden}.card-header{padding:16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.card-header h2{font-size:16px;font-weight:600}.card-body{padding:16px}.endpoint-list{display:flex;flex-direction:column;gap:12px}.endpoint-item{border:1px solid var(--border);border-radius:6px;overflow:hidden}.endpoint-header{padding:12px 16px;background:var(--bg);display:flex;justify-content:space-between;align-items:center;cursor:pointer}.endpoint-header:hover{background:#e5e7eb}.endpoint-info{display:flex;align-items:center;gap:12px}.method-badge{padding:2px 8px;border-radius:4px;font-size:12px;font-weight:600;text-transform:uppercase}.method-GET{background:#dbeafe;color:#1d4ed8}.method-POST{background:#dcfce7;color:#16a34a}.method-PUT{background:#fef3c7;color:#d97706}.method-DELETE{background:#fee2e2;color:#dc2626}.method-PATCH{background:#f3e8ff;color:#9333ea}.endpoint-path{font-family:SF Mono,Monaco,monospace;font-size:13px}.endpoint-id{font-size:12px;color:var(--text-muted)}.scenarios-list{padding:12px 16px;border-top:1px solid var(--border)}.scenario-item{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--border)}.scenario-item:last-child{border-bottom:none}.scenario-info{display:flex;align-items:center;gap:8px}.scenario-name{font-size:14px}.badge{padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;text-transform:uppercase}.badge-default{background:#dbeafe;color:#1d4ed8}.badge-active{background:#dcfce7;color:#16a34a}.sidebar{display:flex;flex-direction:column;gap:24px}.state-panel .state-item{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--border)}.state-panel .state-item:last-child{border-bottom:none}.state-label{font-size:13px;color:var(--text-muted)}.state-value{font-size:13px;font-weight:500}.flow-list{display:flex;flex-direction:column;gap:8px}.flow-item{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border:1px solid var(--border);border-radius:6px;cursor:pointer;transition:all .15s ease}.flow-item:hover{border-color:var(--primary)}.flow-item.active{border-color:var(--success);background:#f0fdf4}.flow-name{font-size:14px;font-weight:500}.flow-description{font-size:12px;color:var(--text-muted);margin-top:2px}.flows-card{display:flex;flex-direction:column;max-height:600px}.flows-body{flex:1;overflow-y:auto;min-height:0}.active-flow-banner{padding:8px 12px;background:#f0fdf4;border-bottom:1px solid #bbf7d0;display:flex;align-items:center;gap:8px;font-size:13px}.active-flow-label{color:var(--success);font-weight:600}.active-flow-name{font-weight:500}.active-flow-category{color:var(--text-muted)}.flows-search{padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;position:relative}.flows-search-input{flex:1;padding:6px 28px 6px 10px;border:1px solid var(--border);border-radius:4px;font-size:13px}.flows-search-input:focus{outline:none;border-color:var(--primary)}.flows-search-clear{position:absolute;right:20px;background:none;border:none;font-size:18px;color:var(--text-muted);cursor:pointer;padding:0 4px;line-height:1}.flows-search-clear:hover{color:var(--text)}.flow-groups{display:flex;flex-direction:column}.flow-group{border-bottom:1px solid var(--border)}.flow-group:last-child{border-bottom:none}.flow-group-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;-webkit-user-select:none;user-select:none;background:var(--bg-primary);transition:background .15s ease}.flow-group-header:hover{background:var(--bg-secondary)}.flow-group-header.has-active{background:#f0fdf4}.flow-group-toggle{font-size:10px;color:var(--text-muted);width:12px}.flow-group-name{flex:1;font-weight:600;font-size:13px}.flow-group-count{background:var(--bg-secondary);color:var(--text-muted);font-size:11px;padding:2px 6px;border-radius:10px;font-weight:500}.flow-group-items{display:flex;flex-direction:column;gap:4px;padding:8px 12px;background:var(--bg-secondary)}.flow-group-items .flow-item{background:var(--bg-primary);padding:8px 10px}.flow-item-content{flex:1;min-width:0}.flow-meta{display:flex;gap:8px;margin-top:4px}.flow-endpoints{font-size:11px;color:var(--text-muted)}.loading{display:flex;justify-content:center;align-items:center;padding:40px;color:var(--text-muted)}.error{padding:16px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:var(--error)}.empty{padding:24px;text-align:center;color:var(--text-muted)}@media(max-width:900px){.grid{grid-template-columns:1fr}.sidebar{order:-1}}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-success{background:var(--success);color:#fff}.btn-success:hover:not(:disabled){background:#059669}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;justify-content:center;align-items:center;z-index:1000;padding:20px}.modal{background:var(--card-bg);border-radius:12px;box-shadow:0 20px 40px #0003;width:100%;max-width:700px;max-height:90vh;display:flex;flex-direction:column;overflow:hidden}.modal-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.modal-header h2{font-size:18px;font-weight:600;margin:0}.modal-close{background:none;border:none;font-size:24px;cursor:pointer;color:var(--text-muted);padding:0;line-height:1}.modal-close:hover{color:var(--text)}.modal-body{padding:20px;overflow-y:auto;flex:1}.modal-footer{padding:16px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:12px}.modal-error{margin-top:12px;padding:10px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:var(--error);font-size:13px}.json-editor{display:flex;flex-direction:column;gap:8px}.json-editor-header{display:flex;justify-content:space-between;align-items:center}.json-editor-label{font-size:14px;font-weight:500;color:var(--text)}.json-editor-textarea{width:100%;padding:12px;font-family:SF Mono,Monaco,Cascadia Code,monospace;font-size:13px;line-height:1.5;border:1px solid var(--border);border-radius:6px;background:#fafafa;color:var(--text);resize:vertical;outline:none;transition:border-color .15s ease}.json-editor-textarea:focus{border-color:var(--primary);box-shadow:0 0 0 3px #4f46e51a}.json-editor-textarea.json-editor-error{border-color:var(--error)}.json-editor-textarea.json-editor-error:focus{box-shadow:0 0 0 3px #ef44441a}.json-editor-error-message{padding:8px 10px;background:#fef2f2;border:1px solid #fecaca;border-radius:4px;color:var(--error);font-size:12px;font-family:SF Mono,Monaco,Cascadia Code,monospace}.endpoint-header-actions{display:flex;gap:8px}.endpoint-actions{display:flex;gap:6px;margin-left:auto}.btn-icon{padding:4px 8px;font-size:12px;background:transparent;border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-muted)}.btn-icon:hover{background:var(--bg);color:var(--text)}.btn-icon.btn-danger{border-color:transparent}.btn-icon.btn-danger:hover{background:#fef2f2;color:var(--error);border-color:#fecaca}.confirm-dialog{text-align:center}.confirm-dialog p{margin:0 0 20px;font-size:15px;color:var(--text)}.confirm-dialog .btn-group{display:flex;justify-content:center;gap:12px}.flow-item.has-actions{cursor:default}.flow-item.has-actions .flow-item-content{cursor:pointer}.flow-item-actions{display:flex;align-items:center;gap:8px}.flow-action-buttons{display:flex;gap:4px;opacity:0;transition:opacity .15s ease}.flow-item:hover .flow-action-buttons{opacity:1}.btn-danger-icon:hover{color:var(--error)}.card-header-actions{display:flex;gap:8px}.modal-large{max-width:900px}.flow-editor-modal .modal-body{display:flex;flex-direction:column;gap:20px}.flow-editor-fields{display:flex;flex-direction:column;gap:16px}.form-row{display:flex;gap:16px}.form-group{flex:1;display:flex;flex-direction:column;gap:6px}.form-group-wide{flex:2}.form-group label{font-size:13px;font-weight:500;color:var(--text)}.form-group input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;font-size:14px;transition:border-color .15s ease;box-sizing:border-box}.form-group input:focus{outline:none;border-color:var(--primary)}.form-group input.disabled{background:var(--bg-secondary);color:var(--text-muted);cursor:not-allowed}.form-hint{font-size:11px;color:var(--text-muted)}.required{color:var(--error)}.category-input,.category-input-wrapper{position:relative}.category-input-wrapper input{width:100%}.category-suggestions{position:absolute;top:100%;left:0;right:0;background:var(--card-bg);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px #0000001a;z-index:10;max-height:150px;overflow-y:auto;margin-top:4px}.category-suggestion{display:block;width:100%;text-align:left;padding:8px 12px;background:none;border:none;font-size:13px;cursor:pointer}.category-suggestion:hover{background:var(--bg-secondary)}.flow-editor-picker{border-top:1px solid var(--border);padding-top:20px}.flow-editor-picker h4{margin:0 0 4px;font-size:15px;font-weight:600}.picker-description{font-size:13px;color:var(--text-muted);margin:0 0 12px}.endpoint-scenario-picker{border:1px solid var(--border);border-radius:8px;overflow:hidden}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;background:var(--bg-secondary);border-bottom:1px solid var(--border)}.picker-stats{font-size:13px;color:var(--text-muted)}.picker-actions{display:flex;gap:8px}.picker-search{padding:8px 12px;border-bottom:1px solid var(--border);position:relative}.picker-search-input{width:100%;padding:6px 28px 6px 10px;border:1px solid var(--border);border-radius:4px;font-size:13px}.picker-search-input:focus{outline:none;border-color:var(--primary)}.picker-search-clear{position:absolute;right:20px;top:50%;transform:translateY(-50%);background:none;border:none;font-size:18px;color:var(--text-muted);cursor:pointer;padding:0 4px;line-height:1}.picker-list{max-height:300px;overflow-y:auto}.picker-empty{padding:24px;text-align:center;color:var(--text-muted)}.picker-endpoint{border-bottom:1px solid var(--border)}.picker-endpoint:last-child{border-bottom:none}.picker-endpoint.has-selection{background:#f0fdf4}.picker-endpoint-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;-webkit-user-select:none;user-select:none}.picker-endpoint-header:hover{background:var(--bg-secondary)}.picker-toggle{font-size:10px;color:var(--text-muted);width:12px}.picker-endpoint-path{font-family:SF Mono,Monaco,monospace;font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.picker-selected-badge{font-size:11px;padding:2px 6px;background:var(--success);color:#fff;border-radius:4px}.picker-scenarios{padding:8px 12px 8px 32px;background:var(--bg-secondary);display:flex;flex-direction:column;gap:6px}.picker-scenario{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--card-bg);border:1px solid var(--border);border-radius:4px;cursor:pointer;transition:all .15s ease}.picker-scenario:hover{border-color:var(--primary)}.picker-scenario.selected{border-color:var(--success);background:#f0fdf4}.picker-scenario input[type=radio]{margin:0}.picker-scenario-name{font-size:13px;font-weight:500}.picker-scenario-id{font-size:11px;color:var(--text-muted);font-family:SF Mono,Monaco,monospace}.picker-scenario-default{font-size:10px;padding:1px 4px;background:var(--primary);color:#fff;border-radius:3px;margin-left:auto}.picker-remove{padding:4px 8px;color:var(--text-muted);font-size:12px}.btn-link{background:none;border:none;color:var(--primary);cursor:pointer;text-decoration:none}.btn-link:hover{text-decoration:underline}
|
|
1
|
+
:root{--primary: #4f46e5;--primary-hover: #4338ca;--success: #10b981;--warning: #f59e0b;--error: #ef4444;--bg: #f5f5f5;--card-bg: #ffffff;--border: #e5e7eb;--text: #1f2937;--text-muted: #6b7280}*{box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background:var(--bg);color:var(--text);margin:0;padding:0}.app{max-width:1200px;margin:0 auto;padding:20px}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}.header h1{font-size:24px;font-weight:600;color:var(--text)}.header-actions{display:flex;gap:12px}.btn{padding:8px 16px;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:all .15s ease}.btn-primary{background:var(--primary);color:#fff}.btn-primary:hover{background:var(--primary-hover)}.btn-secondary{background:var(--card-bg);color:var(--text);border:1px solid var(--border)}.btn-secondary:hover{background:var(--bg)}.btn-danger{background:var(--error);color:#fff}.btn-danger:hover{background:#dc2626}.btn-sm{padding:4px 10px;font-size:12px}.grid{display:grid;grid-template-columns:1fr 300px;gap:24px}.main-content{display:flex;flex-direction:column;gap:24px}.card{background:var(--card-bg);border-radius:8px;border:1px solid var(--border);overflow:hidden}.card-header{padding:16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.card-header h2{font-size:16px;font-weight:600}.card-body{padding:16px}.endpoint-list{display:flex;flex-direction:column;gap:12px}.endpoint-item{border:1px solid var(--border);border-radius:6px;overflow:hidden}.endpoint-header{padding:12px 16px;background:var(--bg);display:flex;justify-content:space-between;align-items:center;cursor:pointer}.endpoint-header:hover{background:#e5e7eb}.endpoint-info{display:flex;align-items:center;gap:12px}.method-badge{padding:2px 8px;border-radius:4px;font-size:12px;font-weight:600;text-transform:uppercase}.method-GET{background:#dbeafe;color:#1d4ed8}.method-POST{background:#dcfce7;color:#16a34a}.method-PUT{background:#fef3c7;color:#d97706}.method-DELETE{background:#fee2e2;color:#dc2626}.method-PATCH{background:#f3e8ff;color:#9333ea}.endpoint-path{font-family:SF Mono,Monaco,monospace;font-size:13px}.endpoint-id{font-size:12px;color:var(--text-muted)}.scenarios-list{padding:12px 16px;border-top:1px solid var(--border)}.scenario-item{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--border)}.scenario-item:last-child{border-bottom:none}.scenario-info{display:flex;align-items:center;gap:8px}.scenario-name{font-size:14px}.badge{padding:2px 6px;border-radius:4px;font-size:10px;font-weight:600;text-transform:uppercase}.badge-default{background:#dbeafe;color:#1d4ed8}.badge-active{background:#dcfce7;color:#16a34a}.sidebar{display:flex;flex-direction:column;gap:24px}.state-panel .state-item{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--border)}.state-panel .state-item:last-child{border-bottom:none}.state-label{font-size:13px;color:var(--text-muted)}.state-value{font-size:13px;font-weight:500}.flow-list{display:flex;flex-direction:column;gap:8px}.flow-item{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border:1px solid var(--border);border-radius:6px;cursor:pointer;transition:all .15s ease}.flow-item:hover{border-color:var(--primary)}.flow-item.active{border-color:var(--success);background:#f0fdf4}.flow-name{font-size:14px;font-weight:500}.flow-description{font-size:12px;color:var(--text-muted);margin-top:2px}.flows-card{display:flex;flex-direction:column;max-height:600px}.flows-body{flex:1;overflow-y:auto;min-height:0}.active-flow-banner{padding:8px 12px;background:#f0fdf4;border-bottom:1px solid #bbf7d0;display:flex;align-items:center;gap:8px;font-size:13px}.active-flow-label{color:var(--success);font-weight:600}.active-flow-name{font-weight:500}.active-flow-category{color:var(--text-muted)}.flows-search{padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;position:relative}.flows-search-input{flex:1;padding:6px 28px 6px 10px;border:1px solid var(--border);border-radius:4px;font-size:13px}.flows-search-input:focus{outline:none;border-color:var(--primary)}.flows-search-clear{position:absolute;right:20px;background:none;border:none;font-size:18px;color:var(--text-muted);cursor:pointer;padding:0 4px;line-height:1}.flows-search-clear:hover{color:var(--text)}.flow-groups{display:flex;flex-direction:column}.flow-group{border-bottom:1px solid var(--border)}.flow-group:last-child{border-bottom:none}.flow-group-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;-webkit-user-select:none;user-select:none;background:var(--bg-primary);transition:background .15s ease}.flow-group-header:hover{background:var(--bg-secondary)}.flow-group-header.has-active{background:#f0fdf4}.flow-group-toggle{font-size:10px;color:var(--text-muted);width:12px}.flow-group-name{flex:1;font-weight:600;font-size:13px}.flow-group-count{background:var(--bg-secondary);color:var(--text-muted);font-size:11px;padding:2px 6px;border-radius:10px;font-weight:500}.flow-group-items{display:flex;flex-direction:column;gap:4px;padding:8px 12px;background:var(--bg-secondary)}.flow-group-items .flow-item{background:var(--bg-primary);padding:8px 10px}.flow-item-content{flex:1;min-width:0}.flow-meta{display:flex;gap:8px;margin-top:4px}.flow-endpoints{font-size:11px;color:var(--text-muted)}.loading{display:flex;justify-content:center;align-items:center;padding:40px;color:var(--text-muted)}.error{padding:16px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:var(--error)}.empty{padding:24px;text-align:center;color:var(--text-muted)}@media(max-width:900px){.grid{grid-template-columns:1fr}.sidebar{order:-1}}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-success{background:var(--success);color:#fff}.btn-success:hover:not(:disabled){background:#059669}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;justify-content:center;align-items:center;z-index:1000;padding:20px}.modal{background:var(--card-bg);border-radius:12px;box-shadow:0 20px 40px #0003;width:100%;max-width:700px;max-height:90vh;display:flex;flex-direction:column;overflow:hidden}.modal-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.modal-header h2{font-size:18px;font-weight:600;margin:0}.modal-close{background:none;border:none;font-size:24px;cursor:pointer;color:var(--text-muted);padding:0;line-height:1}.modal-close:hover{color:var(--text)}.modal-body{padding:20px;overflow-y:auto;flex:1}.modal-footer{padding:16px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:12px}.modal-error{margin-top:12px;padding:10px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:var(--error);font-size:13px}.endpoint-actions{display:flex;gap:6px;margin-left:auto}.btn-icon{padding:4px 8px;font-size:12px;background:transparent;border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-muted)}.btn-icon:hover{background:var(--bg);color:var(--text)}.btn-icon.btn-danger{border-color:transparent}.btn-icon.btn-danger:hover{background:#fef2f2;color:var(--error);border-color:#fecaca}.confirm-dialog{text-align:center}.confirm-dialog p{margin:0 0 20px;font-size:15px;color:var(--text)}.confirm-dialog .btn-group{display:flex;justify-content:center;gap:12px}.flow-item.has-actions{cursor:default}.flow-item.has-actions .flow-item-content{cursor:pointer}.flow-item-actions{display:flex;align-items:center;gap:8px}.flow-action-buttons{display:flex;gap:4px;opacity:0;transition:opacity .15s ease}.flow-item:hover .flow-action-buttons{opacity:1}.btn-danger-icon:hover{color:var(--error)}.card-header-actions{display:flex;gap:8px}.modal-large{max-width:900px}.flow-editor-modal .modal-body{display:flex;flex-direction:column;gap:20px}.flow-editor-fields{display:flex;flex-direction:column;gap:16px}.form-row{display:flex;gap:16px}.form-group{flex:1;display:flex;flex-direction:column;gap:6px}.form-group-wide{flex:2}.form-group label{font-size:13px;font-weight:500;color:var(--text)}.form-group input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;font-size:14px;transition:border-color .15s ease;box-sizing:border-box}.form-group input:focus{outline:none;border-color:var(--primary)}.form-group input.disabled{background:var(--bg-secondary);color:var(--text-muted);cursor:not-allowed}.form-hint{font-size:11px;color:var(--text-muted)}.required{color:var(--error)}.category-input,.category-input-wrapper{position:relative}.category-input-wrapper input{width:100%}.category-suggestions{position:absolute;top:100%;left:0;right:0;background:var(--card-bg);border:1px solid var(--border);border-radius:6px;box-shadow:0 4px 12px #0000001a;z-index:10;max-height:150px;overflow-y:auto;margin-top:4px}.category-suggestion{display:block;width:100%;text-align:left;padding:8px 12px;background:none;border:none;font-size:13px;cursor:pointer}.category-suggestion:hover{background:var(--bg-secondary)}.flow-editor-picker{border-top:1px solid var(--border);padding-top:20px}.flow-editor-picker h4{margin:0 0 4px;font-size:15px;font-weight:600}.picker-description{font-size:13px;color:var(--text-muted);margin:0 0 12px}.endpoint-scenario-picker{border:1px solid var(--border);border-radius:8px;overflow:hidden}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;background:var(--bg-secondary);border-bottom:1px solid var(--border)}.picker-stats{font-size:13px;color:var(--text-muted)}.picker-actions{display:flex;gap:8px}.picker-search{padding:8px 12px;border-bottom:1px solid var(--border);position:relative}.picker-search-input{width:100%;padding:6px 28px 6px 10px;border:1px solid var(--border);border-radius:4px;font-size:13px}.picker-search-input:focus{outline:none;border-color:var(--primary)}.picker-search-clear{position:absolute;right:20px;top:50%;transform:translateY(-50%);background:none;border:none;font-size:18px;color:var(--text-muted);cursor:pointer;padding:0 4px;line-height:1}.picker-list{max-height:300px;overflow-y:auto}.picker-empty{padding:24px;text-align:center;color:var(--text-muted)}.picker-endpoint{border-bottom:1px solid var(--border)}.picker-endpoint:last-child{border-bottom:none}.picker-endpoint.has-selection{background:#f0fdf4}.picker-endpoint-header{display:flex;align-items:center;gap:8px;padding:10px 12px;cursor:pointer;-webkit-user-select:none;user-select:none}.picker-endpoint-header:hover{background:var(--bg-secondary)}.picker-toggle{font-size:10px;color:var(--text-muted);width:12px}.picker-endpoint-path{font-family:SF Mono,Monaco,monospace;font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.picker-selected-badge{font-size:11px;padding:2px 6px;background:var(--success);color:#fff;border-radius:4px}.picker-scenarios{padding:8px 12px 8px 32px;background:var(--bg-secondary);display:flex;flex-direction:column;gap:6px}.picker-scenario{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--card-bg);border:1px solid var(--border);border-radius:4px;cursor:pointer;transition:all .15s ease}.picker-scenario:hover{border-color:var(--primary)}.picker-scenario.selected{border-color:var(--success);background:#f0fdf4}.picker-scenario input[type=radio]{margin:0}.picker-scenario-name{font-size:13px;font-weight:500}.picker-scenario-id{font-size:11px;color:var(--text-muted);font-family:SF Mono,Monaco,monospace}.picker-scenario-default{font-size:10px;padding:1px 4px;background:var(--primary);color:#fff;border-radius:3px;margin-left:auto}.picker-remove{padding:4px 8px;color:var(--text-muted);font-size:12px}.btn-link{background:none;border:none;color:var(--primary);cursor:pointer;text-decoration:none}.btn-link:hover{text-decoration:underline}
|