@zze/mock-server 0.5.0 → 0.6.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/dist/__tests__/mock-server.test.js +6 -1
- package/dist/admin/__tests__/admin-router.test.js +2 -0
- package/dist/admin/admin-router.d.ts +4 -4
- package/dist/admin/admin-router.d.ts.map +1 -1
- package/dist/admin/admin-router.js +1 -0
- package/dist/admin/types.d.ts +1 -0
- package/dist/admin/types.d.ts.map +1 -1
- package/dist/config/__tests__/config-loader.test.js +18 -0
- package/dist/config/config-loader.d.ts +3 -3
- package/dist/config/config-loader.d.ts.map +1 -1
- package/dist/config/config-loader.js +4 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/types.d.ts +9 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/server/__tests__/mock-server-app.test.js +3 -4
- package/dist/server/mock-server-app.d.ts +4 -4
- package/dist/server/mock-server-app.d.ts.map +1 -1
- package/dist/server/mock-server-app.js +0 -2
- package/dist/state/__tests__/state-manager.test.js +33 -0
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +9 -6
- package/dist/ui/assets/index-C2lhc6Pd.css +1 -0
- package/dist/ui/assets/index-D8nVOwBC.js +9 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -2
- package/dist/ui/assets/index-C5BZSa2z.css +0 -1
- package/dist/ui/assets/index-DY4CS66u.js +0 -9
|
@@ -231,7 +231,10 @@ const supertest_1 = __importDefault(require("supertest"));
|
|
|
231
231
|
}
|
|
232
232
|
});
|
|
233
233
|
(0, vitest_1.it)('should reload configuration manually', async () => {
|
|
234
|
-
await createEndpoint('test1', '/test1'
|
|
234
|
+
await createEndpoint('test1', '/test1', 'GET', [
|
|
235
|
+
{ id: 'success', name: 'Success', status: 200, body: { message: 'ok' }, isDefault: true },
|
|
236
|
+
{ id: 'error', name: 'Error', status: 500, body: { error: 'failed' } },
|
|
237
|
+
]);
|
|
235
238
|
server = new mock_server_js_1.MockServer({
|
|
236
239
|
configPath: configDir,
|
|
237
240
|
port: 3015,
|
|
@@ -239,10 +242,12 @@ const supertest_1 = __importDefault(require("supertest"));
|
|
|
239
242
|
});
|
|
240
243
|
await server.start();
|
|
241
244
|
(0, vitest_1.expect)(server.getEndpoints().length).toBe(1);
|
|
245
|
+
server.activateScenario('test1', 'error');
|
|
242
246
|
// Add another endpoint
|
|
243
247
|
await createEndpoint('test2', '/test2');
|
|
244
248
|
await server.reload();
|
|
245
249
|
(0, vitest_1.expect)(server.getEndpoints().length).toBe(2);
|
|
250
|
+
(0, vitest_1.expect)(server.getState().activeScenarios.test1).toBe('error');
|
|
246
251
|
});
|
|
247
252
|
(0, vitest_1.it)('should throw if server is not running', async () => {
|
|
248
253
|
server = new mock_server_js_1.MockServer({
|
|
@@ -10,6 +10,7 @@ const mock_server_app_js_1 = require("../../server/mock-server-app.js");
|
|
|
10
10
|
const mockEndpoints = [
|
|
11
11
|
{
|
|
12
12
|
id: 'get-users',
|
|
13
|
+
sourceFile: 'accounts/users.ts',
|
|
13
14
|
path: '/users',
|
|
14
15
|
method: 'GET',
|
|
15
16
|
scenarios: [
|
|
@@ -74,6 +75,7 @@ const mockFlows = [
|
|
|
74
75
|
(0, vitest_1.expect)(usersEndpoint).toBeDefined();
|
|
75
76
|
(0, vitest_1.expect)(usersEndpoint.path).toBe('/users');
|
|
76
77
|
(0, vitest_1.expect)(usersEndpoint.method).toBe('GET');
|
|
78
|
+
(0, vitest_1.expect)(usersEndpoint.sourceFile).toBe('accounts/users.ts');
|
|
77
79
|
(0, vitest_1.expect)(usersEndpoint.scenarios).toHaveLength(2);
|
|
78
80
|
(0, vitest_1.expect)(usersEndpoint.defaultScenarioId).toBe('success');
|
|
79
81
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FlowConfig } from '../schemas/index.js';
|
|
3
3
|
import type { StateManager } from '../state/index.js';
|
|
4
|
-
import type { ConfigWriter } from '../config/index.js';
|
|
4
|
+
import type { ConfigWriter, RuntimeEndpointConfig } from '../config/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* Options for creating the admin router
|
|
7
7
|
*/
|
|
8
8
|
export interface AdminRouterOptions {
|
|
9
|
-
getEndpoints: () =>
|
|
9
|
+
getEndpoints: () => RuntimeEndpointConfig[];
|
|
10
10
|
getFlows: () => FlowConfig[];
|
|
11
11
|
stateManager: StateManager;
|
|
12
12
|
getConfigWriter?: () => ConfigWriter | undefined;
|
|
@@ -19,7 +19,7 @@ export declare function createAdminRouter(options: AdminRouterOptions): Router;
|
|
|
19
19
|
/**
|
|
20
20
|
* @deprecated Use options object instead
|
|
21
21
|
*/
|
|
22
|
-
export declare function createAdminRouter(getEndpoints: () =>
|
|
22
|
+
export declare function createAdminRouter(getEndpoints: () => RuntimeEndpointConfig[], getFlows: () => FlowConfig[], stateManager: StateManager): Router;
|
|
23
23
|
/**
|
|
24
24
|
* Admin API path constant
|
|
25
25
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-router.d.ts","sourceRoot":"","sources":["../../src/admin/admin-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAmC,MAAM,SAAS,CAAC;AAClE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"admin-router.d.ts","sourceRoot":"","sources":["../../src/admin/admin-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAmC,MAAM,SAAS,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAW9E;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,qBAAqB,EAAE,CAAC;IAC5C,QAAQ,EAAE,MAAM,UAAU,EAAE,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,YAAY,GAAG,SAAS,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;CAC7D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAAC;AACvE;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,qBAAqB,EAAE,EAC3C,QAAQ,EAAE,MAAM,UAAU,EAAE,EAC5B,YAAY,EAAE,YAAY,GACzB,MAAM,CAAC;AA2cV;;GAEG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC"}
|
|
@@ -43,6 +43,7 @@ function createAdminRouter(optionsOrGetEndpoints, getFlowsArg, stateManagerArg)
|
|
|
43
43
|
const response = {
|
|
44
44
|
endpoints: endpoints.map((endpoint) => ({
|
|
45
45
|
id: endpoint.id,
|
|
46
|
+
sourceFile: endpoint.sourceFile,
|
|
46
47
|
path: endpoint.path,
|
|
47
48
|
method: endpoint.method,
|
|
48
49
|
scenarios: endpoint.scenarios.map((scenario) => ({
|
package/dist/admin/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/admin/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;EAGxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;EAEpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;EAAmB,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;EAGrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,KAAK,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,KAAK,CAAC;YACf,EAAE,EAAE,MAAM,CAAC;YACX,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC,CAAC;QACH,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC3C,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/admin/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;EAGxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF;;GAEG;AACH,eAAO,MAAM,yBAAyB;;;;;;EAEpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;EAAmB,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;EAGrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,KAAK,CAAC;QACf,EAAE,EAAE,MAAM,CAAC;QACX,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,KAAK,CAAC;YACf,EAAE,EAAE,MAAM,CAAC;YACX,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;SAChB,CAAC,CAAC;QACH,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC3C,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
|
|
@@ -6,6 +6,7 @@ const config_loader_js_1 = require("../config-loader.js");
|
|
|
6
6
|
const fixturesPath = (0, path_1.join)(__dirname, 'fixtures');
|
|
7
7
|
const validConfigPath = (0, path_1.join)(fixturesPath, 'mock-config');
|
|
8
8
|
const invalidConfigPath = (0, path_1.join)(fixturesPath, 'invalid-config');
|
|
9
|
+
const groupedConfigPath = (0, path_1.join)(fixturesPath, 'grouped-config');
|
|
9
10
|
(0, vitest_1.describe)('ConfigLoader', () => {
|
|
10
11
|
(0, vitest_1.describe)('constructor', () => {
|
|
11
12
|
(0, vitest_1.it)('should resolve config path', () => {
|
|
@@ -49,6 +50,23 @@ const invalidConfigPath = (0, path_1.join)(fixturesPath, 'invalid-config');
|
|
|
49
50
|
(0, vitest_1.expect)(ordersEndpoint).toBeDefined();
|
|
50
51
|
(0, vitest_1.expect)(ordersEndpoint?.path).toBe('/orders');
|
|
51
52
|
});
|
|
53
|
+
(0, vitest_1.it)('should retain the source file for each endpoint', async () => {
|
|
54
|
+
const loader = new config_loader_js_1.ConfigLoader(validConfigPath);
|
|
55
|
+
const result = await loader.loadEndpoints();
|
|
56
|
+
(0, vitest_1.expect)(result.endpoints.find((endpoint) => endpoint.id === 'get-users')?.sourceFile)
|
|
57
|
+
.toBe('users.json');
|
|
58
|
+
(0, vitest_1.expect)(result.endpoints.find((endpoint) => endpoint.id === 'get-products')?.sourceFile)
|
|
59
|
+
.toBe('products.ts');
|
|
60
|
+
(0, vitest_1.expect)(result.endpoints.find((endpoint) => endpoint.id === 'create-order')?.sourceFile)
|
|
61
|
+
.toBe('orders.js');
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.it)('should retain the full relative source path for endpoint arrays', async () => {
|
|
64
|
+
const loader = new config_loader_js_1.ConfigLoader(groupedConfigPath);
|
|
65
|
+
const result = await loader.loadEndpoints();
|
|
66
|
+
(0, vitest_1.expect)(result.endpoints).toHaveLength(2);
|
|
67
|
+
(0, vitest_1.expect)(result.endpoints.map((endpoint) => endpoint.sourceFile))
|
|
68
|
+
.toEqual(['accounts/users.json', 'accounts/users.json']);
|
|
69
|
+
});
|
|
52
70
|
(0, vitest_1.it)('should return empty array for non-existent directory', async () => {
|
|
53
71
|
const loader = new config_loader_js_1.ConfigLoader('/non/existent/path');
|
|
54
72
|
const result = await loader.loadEndpoints();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ConfigLoadResult } from './types.js';
|
|
1
|
+
import type { FlowConfig } from '../schemas/index.js';
|
|
2
|
+
import type { ConfigLoadResult, LoadedEndpointConfig } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Configuration loader that discovers and loads endpoint and flow configs
|
|
5
5
|
*/
|
|
@@ -24,7 +24,7 @@ export declare class ConfigLoader {
|
|
|
24
24
|
* Load all endpoint configurations from the endpoints directory
|
|
25
25
|
*/
|
|
26
26
|
loadEndpoints(): Promise<{
|
|
27
|
-
endpoints:
|
|
27
|
+
endpoints: LoadedEndpointConfig[];
|
|
28
28
|
errors: Array<{
|
|
29
29
|
filePath: string;
|
|
30
30
|
error: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config/config-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config/config-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAkB,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAatE,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEzE;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;gBAEb,UAAU,EAAE,MAAM;IAO9B;;OAEG;IACH,aAAa,IAAI,MAAM;IAIvB;;OAEG;IACH,eAAe,IAAI,MAAM;IAIzB;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC;QAC7B,SAAS,EAAE,oBAAoB,EAAE,CAAC;QAClC,MAAM,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACpD,CAAC;IA+CF;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC;QACzB,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,MAAM,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACpD,CAAC;IA4CF;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA6CvC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,gBAAgB,CAAC;CAG1C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAEnE"}
|
|
@@ -66,7 +66,10 @@ class ConfigLoader {
|
|
|
66
66
|
});
|
|
67
67
|
continue;
|
|
68
68
|
}
|
|
69
|
-
endpoints.push(
|
|
69
|
+
endpoints.push({
|
|
70
|
+
...validationResult.data,
|
|
71
|
+
sourceFile: (0, path_1.relative)(this.endpointsDir, filePath).split(path_1.sep).join('/'),
|
|
72
|
+
});
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
return { endpoints, errors };
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { MockServerOptions, LoadedConfig, FileLoadResult, ConfigLoadResult, SupportedExtension, } from './types.js';
|
|
1
|
+
export type { MockServerOptions, LoadedConfig, LoadedEndpointConfig, RuntimeEndpointConfig, FileLoadResult, ConfigLoadResult, SupportedExtension, } from './types.js';
|
|
2
2
|
export { SUPPORTED_EXTENSIONS } from './types.js';
|
|
3
3
|
export { loadConfigFile, isSupportedExtension, clearModuleCache, } from './file-loader.js';
|
|
4
4
|
export { discoverConfigFiles, directoryExists, getConfigDirectories, type DiscoveryOptions, } from './config-discovery.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGtE,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GACjB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGtE,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GACjB,MAAM,oBAAoB,CAAC"}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { EndpointConfig, FlowConfig } from '../schemas/index.js';
|
|
2
|
+
export type RuntimeEndpointConfig = EndpointConfig & {
|
|
3
|
+
sourceFile?: string;
|
|
4
|
+
};
|
|
5
|
+
/** Validated endpoint config with loader-derived metadata */
|
|
6
|
+
export type LoadedEndpointConfig = RuntimeEndpointConfig & {
|
|
7
|
+
/** Path relative to the endpoints directory, normalized for API consumers */
|
|
8
|
+
sourceFile: string;
|
|
9
|
+
};
|
|
2
10
|
/**
|
|
3
11
|
* Options for initializing the MockServer
|
|
4
12
|
*/
|
|
@@ -16,7 +24,7 @@ export interface MockServerOptions {
|
|
|
16
24
|
* Loaded configuration from disk
|
|
17
25
|
*/
|
|
18
26
|
export interface LoadedConfig {
|
|
19
|
-
endpoints:
|
|
27
|
+
endpoints: LoadedEndpointConfig[];
|
|
20
28
|
flows: FlowConfig[];
|
|
21
29
|
}
|
|
22
30
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,6DAA6D;AAC7D,MAAM,MAAM,oBAAoB,GAAG,qBAAqB,GAAG;IACzD,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,kDAAmD,CAAC;AAErF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -267,14 +267,13 @@ const mockFlows = [
|
|
|
267
267
|
.expect(200);
|
|
268
268
|
(0, vitest_1.expect)(res.body).toEqual({ new: true });
|
|
269
269
|
});
|
|
270
|
-
(0, vitest_1.it)('should
|
|
270
|
+
(0, vitest_1.it)('should preserve valid scenario overrides on reload', async () => {
|
|
271
271
|
serverApp.getStateManager().activateScenario('get-users', 'error');
|
|
272
272
|
serverApp.reloadConfig(mockEndpoints, mockFlows);
|
|
273
|
-
// State should be reset, so default scenario should be active
|
|
274
273
|
const res = await (0, supertest_1.default)(serverApp.getApp())
|
|
275
274
|
.get('/api/users')
|
|
276
|
-
.expect(
|
|
277
|
-
(0, vitest_1.expect)(res.body
|
|
275
|
+
.expect(500);
|
|
276
|
+
(0, vitest_1.expect)(res.body).toEqual({ error: 'Internal Server Error' });
|
|
278
277
|
});
|
|
279
278
|
(0, vitest_1.it)('should remove old routes on reload', async () => {
|
|
280
279
|
serverApp.reloadConfig([], []);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Express } from 'express';
|
|
2
2
|
import type { Server } from 'http';
|
|
3
|
-
import type {
|
|
3
|
+
import type { FlowConfig } from '../schemas/index.js';
|
|
4
4
|
import { StateManager } from '../state/index.js';
|
|
5
5
|
import { ResponseResolver } from '../resolver/index.js';
|
|
6
|
-
import type { ConfigWriter } from '../config/index.js';
|
|
6
|
+
import type { ConfigWriter, RuntimeEndpointConfig } from '../config/index.js';
|
|
7
7
|
import type { ServerOptions, ServerState, RegisteredRoute } from './types.js';
|
|
8
8
|
export declare const ADMIN_UI_PATH = "/mock-admin";
|
|
9
9
|
/**
|
|
@@ -70,7 +70,7 @@ export declare class MockServerApp {
|
|
|
70
70
|
/**
|
|
71
71
|
* Register endpoints and flows
|
|
72
72
|
*/
|
|
73
|
-
registerConfig(endpoints:
|
|
73
|
+
registerConfig(endpoints: RuntimeEndpointConfig[], flows: FlowConfig[]): void;
|
|
74
74
|
/**
|
|
75
75
|
* Register routes for all endpoints
|
|
76
76
|
*/
|
|
@@ -110,7 +110,7 @@ export declare class MockServerApp {
|
|
|
110
110
|
/**
|
|
111
111
|
* Reload configuration (for hot reload support)
|
|
112
112
|
*/
|
|
113
|
-
reloadConfig(endpoints:
|
|
113
|
+
reloadConfig(endpoints: RuntimeEndpointConfig[], flows: FlowConfig[]): void;
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
116
|
* Create a new MockServerApp instance
|
|
@@ -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;AAGnC,OAAO,KAAK,
|
|
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,EAAkB,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,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,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,CAA+B;IAChD,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,qBAAqB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;IAgB7E;;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,qBAAqB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;CAG5E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GACnC,aAAa,CAEf"}
|
|
@@ -67,6 +67,39 @@ const mockFlows = [
|
|
|
67
67
|
(0, vitest_1.expect)(state.activeScenarios).toEqual({ 'get-users': 'error' });
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
|
+
(0, vitest_1.describe)('configuration reconciliation', () => {
|
|
71
|
+
(0, vitest_1.it)('should preserve a valid scenario override when endpoints are replaced', () => {
|
|
72
|
+
stateManager.activateScenario('get-users', 'error');
|
|
73
|
+
stateManager.setEndpoints(mockEndpoints);
|
|
74
|
+
(0, vitest_1.expect)(stateManager.getActiveScenarioId('get-users')).toBe('error');
|
|
75
|
+
});
|
|
76
|
+
(0, vitest_1.it)('should remove an override when its endpoint is removed', () => {
|
|
77
|
+
stateManager.activateScenario('get-users', 'error');
|
|
78
|
+
stateManager.setEndpoints([mockEndpoints[1]]);
|
|
79
|
+
(0, vitest_1.expect)(stateManager.getActiveScenarioId('get-users')).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
(0, vitest_1.it)('should remove an override when its scenario is removed', () => {
|
|
82
|
+
stateManager.activateScenario('get-users', 'error');
|
|
83
|
+
const endpointsWithoutError = mockEndpoints.map((endpoint) => endpoint.id === 'get-users'
|
|
84
|
+
? {
|
|
85
|
+
...endpoint,
|
|
86
|
+
scenarios: endpoint.scenarios.filter((scenario) => scenario.id !== 'error'),
|
|
87
|
+
}
|
|
88
|
+
: endpoint);
|
|
89
|
+
stateManager.setEndpoints(endpointsWithoutError);
|
|
90
|
+
(0, vitest_1.expect)(stateManager.getActiveScenarioId('get-users')).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)('should preserve the active flow when flows are replaced', () => {
|
|
93
|
+
stateManager.activateFlow('admin-flow');
|
|
94
|
+
stateManager.setFlows(mockFlows);
|
|
95
|
+
(0, vitest_1.expect)(stateManager.getActiveFlowId()).toBe('admin-flow');
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.it)('should clear the active flow when it is removed', () => {
|
|
98
|
+
stateManager.activateFlow('admin-flow');
|
|
99
|
+
stateManager.setFlows([mockFlows[1]]);
|
|
100
|
+
(0, vitest_1.expect)(stateManager.getActiveFlowId()).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
70
103
|
(0, vitest_1.describe)('activateScenario', () => {
|
|
71
104
|
(0, vitest_1.it)('should activate a valid scenario', () => {
|
|
72
105
|
stateManager.activateScenario('get-users', 'error');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-manager.d.ts","sourceRoot":"","sources":["../../src/state/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EACV,oBAAoB,EAEpB,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,qBAAa,YAAY;IACvB,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAAkC;IAEzD,+BAA+B;IAC/B,OAAO,CAAC,YAAY,CAAuB;IAE3C,0CAA0C;IAC1C,OAAO,CAAC,SAAS,CAA0C;IAE3D,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAsC;IAEnD,6BAA6B;IAC7B,OAAO,CAAC,SAAS,CAAuC;IAExD;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"state-manager.d.ts","sourceRoot":"","sources":["../../src/state/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EACV,oBAAoB,EAEpB,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,qBAAa,YAAY;IACvB,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAAkC;IAEzD,+BAA+B;IAC/B,OAAO,CAAC,YAAY,CAAuB;IAE3C,0CAA0C;IAC1C,OAAO,CAAC,SAAS,CAA0C;IAE3D,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAsC;IAEnD,6BAA6B;IAC7B,OAAO,CAAC,SAAS,CAAuC;IAExD;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,IAAI;IAW/C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;IAQnC;;OAEG;IACH,QAAQ,IAAI,oBAAoB;IAOhC;;OAEG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,aAAa,IAAI,UAAU,GAAG,IAAI;IAKlC;;;OAGG;IACH,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3D;;;OAGG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAsBpD;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI;IAU/E;;;OAGG;IACH,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAkB9D;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIvC;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAalC;;OAEG;IACH,cAAc,IAAI,IAAI;IAQtB;;;OAGG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAIhD;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAInD;;OAEG;IACH,OAAO,CAAC,IAAI;CAmBb;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,CAEjD"}
|
|
@@ -23,18 +23,21 @@ class StateManager {
|
|
|
23
23
|
* Register endpoints for scenario validation
|
|
24
24
|
*/
|
|
25
25
|
setEndpoints(endpoints) {
|
|
26
|
-
this.endpoints.
|
|
27
|
-
for (const
|
|
28
|
-
this.endpoints.
|
|
26
|
+
this.endpoints = new Map(endpoints.map((endpoint) => [endpoint.id, endpoint]));
|
|
27
|
+
for (const [endpointId, scenarioId] of this.activeScenarios) {
|
|
28
|
+
const endpoint = this.endpoints.get(endpointId);
|
|
29
|
+
if (!endpoint?.scenarios.some((scenario) => scenario.id === scenarioId)) {
|
|
30
|
+
this.activeScenarios.delete(endpointId);
|
|
31
|
+
}
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* Register flows for validation
|
|
33
36
|
*/
|
|
34
37
|
setFlows(flows) {
|
|
35
|
-
this.flows.
|
|
36
|
-
|
|
37
|
-
this.
|
|
38
|
+
this.flows = new Map(flows.map((flow) => [flow.id, flow]));
|
|
39
|
+
if (this.activeFlowId && !this.flows.has(this.activeFlowId)) {
|
|
40
|
+
this.activeFlowId = null;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
/**
|
|
@@ -0,0 +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-groups{display:flex;flex-direction:column;gap:12px}.endpoints-search{padding:10px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;position:relative}.endpoints-search-input{flex:1;min-width:0;padding:8px 32px 8px 10px;border:1px solid var(--border);border-radius:4px;font-size:13px}.endpoints-search-input:focus{outline:none;border-color:var(--primary)}.endpoints-search-clear{position:absolute;right:146px;border:none;background:none;color:var(--text-muted);cursor:pointer;font-size:18px;line-height:1}.endpoint-filter-toggle{display:flex;align-items:center;gap:6px;white-space:nowrap;color:var(--text-muted);font-size:12px;cursor:pointer}.endpoint-filter-toggle input{accent-color:var(--primary)}.endpoint-group{border:1px solid var(--border);border-radius:6px;overflow:hidden}.endpoint-group-header{display:flex;align-items:center;gap:8px;padding:10px 12px;background:var(--bg-secondary);cursor:pointer;-webkit-user-select:none;user-select:none}.endpoint-group-header:hover{background:#e5e7eb}.endpoint-group-toggle{width:12px;color:var(--text-muted);font-size:10px}.endpoint-group-name{flex:1;min-width:0;overflow-wrap:anywhere;font-family:SF Mono,Monaco,monospace;font-size:13px;font-weight:600}.endpoint-group-count{padding:2px 6px;border-radius:10px;background:var(--bg-primary);color:var(--text-muted);font-size:11px;font-weight:500}.endpoint-group-overrides{color:#b45309;font-size:11px;font-weight:600}.endpoint-group-items{display:flex;flex-direction:column;gap:8px;padding:8px}.endpoint-item{border:1px solid var(--border);border-radius:6px;overflow:hidden}.endpoint-item.has-override{border-color:var(--warning)}.endpoint-item.has-override .endpoint-header{background:#fffbeb}.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}.badge-override{background:#fef3c7;color:#b45309}.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}.override-list{display:flex;flex-direction:column;gap:6px;padding-top:10px}.override-empty{color:var(--text-muted);font-size:12px;line-height:1.4}.override-item{width:100%;padding:8px;border:1px solid var(--border);border-radius:4px;background:var(--card-bg);color:var(--text);text-align:left;cursor:pointer}.override-item:hover{border-color:var(--warning);background:#fffbeb}.override-summary,.override-ids{display:block;overflow-wrap:anywhere}.override-summary{display:flex;justify-content:space-between;gap:8px;font-size:12px;font-weight:600}.override-scenario{color:#b45309;text-align:right}.override-ids{margin-top:3px;color:var(--text-muted);font-family:SF Mono,Monaco,monospace;font-size:10px}.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}.endpoints-search{flex-wrap:wrap}.endpoints-search-clear{right:24px;top:16px}.endpoint-filter-toggle{width:100%}}.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}
|