@gxp-dev/tools 2.0.6 → 2.0.7

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.
Files changed (99) hide show
  1. package/browser-extensions/README.md +1 -0
  2. package/browser-extensions/chrome/background.js +857 -0
  3. package/browser-extensions/chrome/content.js +51 -0
  4. package/browser-extensions/chrome/devtools.html +9 -0
  5. package/browser-extensions/chrome/devtools.js +23 -0
  6. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  7. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  8. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  9. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  10. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  11. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  12. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  13. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  14. package/browser-extensions/chrome/inspector.js +1087 -0
  15. package/browser-extensions/chrome/manifest.json +70 -0
  16. package/browser-extensions/chrome/panel.html +638 -0
  17. package/browser-extensions/chrome/panel.js +862 -0
  18. package/browser-extensions/chrome/popup.html +399 -0
  19. package/browser-extensions/chrome/popup.js +515 -0
  20. package/browser-extensions/chrome/rules.json +1 -0
  21. package/browser-extensions/chrome/test-chrome.html +145 -0
  22. package/browser-extensions/chrome/test-mixed-content.html +190 -0
  23. package/browser-extensions/chrome/test-uri-pattern.html +199 -0
  24. package/browser-extensions/firefox/README.md +134 -0
  25. package/browser-extensions/firefox/background.js +804 -0
  26. package/browser-extensions/firefox/content.js +120 -0
  27. package/browser-extensions/firefox/debug-errors.html +229 -0
  28. package/browser-extensions/firefox/debug-https.html +113 -0
  29. package/browser-extensions/firefox/devtools.html +9 -0
  30. package/browser-extensions/firefox/devtools.js +24 -0
  31. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  32. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  33. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  34. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  35. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  36. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  37. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  38. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  39. package/browser-extensions/firefox/inspector.js +1087 -0
  40. package/browser-extensions/firefox/manifest.json +67 -0
  41. package/browser-extensions/firefox/panel.html +638 -0
  42. package/browser-extensions/firefox/panel.js +862 -0
  43. package/browser-extensions/firefox/popup.html +525 -0
  44. package/browser-extensions/firefox/popup.js +536 -0
  45. package/browser-extensions/firefox/test-gramercy.html +126 -0
  46. package/browser-extensions/firefox/test-imports.html +58 -0
  47. package/browser-extensions/firefox/test-masking.html +147 -0
  48. package/browser-extensions/firefox/test-uri-pattern.html +199 -0
  49. package/package.json +7 -2
  50. package/runtime/PortalContainer.vue +326 -0
  51. package/runtime/dev-tools/DevToolsModal.vue +217 -0
  52. package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
  53. package/runtime/dev-tools/MockDataEditor.vue +621 -0
  54. package/runtime/dev-tools/SocketSimulator.vue +562 -0
  55. package/runtime/dev-tools/StoreInspector.vue +644 -0
  56. package/runtime/dev-tools/index.js +6 -0
  57. package/runtime/gxpStringsPlugin.js +428 -0
  58. package/runtime/index.html +22 -0
  59. package/runtime/main.js +32 -0
  60. package/runtime/mock-api/auth-middleware.js +97 -0
  61. package/runtime/mock-api/image-generator.js +221 -0
  62. package/runtime/mock-api/index.js +197 -0
  63. package/runtime/mock-api/response-generator.js +394 -0
  64. package/runtime/mock-api/route-generator.js +323 -0
  65. package/runtime/mock-api/socket-triggers.js +371 -0
  66. package/runtime/mock-api/spec-loader.js +300 -0
  67. package/runtime/server.js +180 -0
  68. package/runtime/stores/gxpPortalConfigStore.js +554 -0
  69. package/runtime/stores/index.js +6 -0
  70. package/runtime/vite-inspector-plugin.js +749 -0
  71. package/runtime/vite-source-tracker-plugin.js +232 -0
  72. package/runtime/vite.config.js +402 -0
  73. package/scripts/launch-chrome.js +90 -0
  74. package/scripts/pack-chrome.js +91 -0
  75. package/socket-events/AiSessionMessageCreated.json +18 -0
  76. package/socket-events/SocialStreamPostCreated.json +24 -0
  77. package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
  78. package/template/README.md +332 -0
  79. package/template/app-manifest.json +32 -0
  80. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  81. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  82. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  83. package/template/dev-assets/images/icon-placeholder.png +0 -0
  84. package/template/dev-assets/images/logo-placeholder.png +0 -0
  85. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  86. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  87. package/template/env.example +51 -0
  88. package/template/gitignore +53 -0
  89. package/template/index.html +22 -0
  90. package/template/main.js +28 -0
  91. package/template/src/DemoPage.vue +459 -0
  92. package/template/src/Plugin.vue +38 -0
  93. package/template/src/stores/index.js +9 -0
  94. package/template/src/stores/test-data.json +173 -0
  95. package/template/theme-layouts/AdditionalStyling.css +0 -0
  96. package/template/theme-layouts/PrivateLayout.vue +39 -0
  97. package/template/theme-layouts/PublicLayout.vue +39 -0
  98. package/template/theme-layouts/SystemLayout.vue +39 -0
  99. package/template/vite.config.js +333 -0
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Response Generator
3
+ *
4
+ * Generates fake JSON data from OpenAPI/JSON Schema definitions
5
+ * using Faker.js for realistic values.
6
+ */
7
+
8
+ const { faker } = require("@faker-js/faker");
9
+
10
+ /**
11
+ * Resolve a $ref reference in the spec
12
+ * @param {string} ref - Reference string (e.g., "#/components/schemas/User")
13
+ * @param {object} spec - Full OpenAPI spec
14
+ * @returns {object|null} Resolved schema or null
15
+ */
16
+ function resolveRef(ref, spec) {
17
+ if (!ref || !ref.startsWith("#/")) {
18
+ return null;
19
+ }
20
+
21
+ const parts = ref.slice(2).split("/");
22
+ let current = spec;
23
+
24
+ for (const part of parts) {
25
+ if (current && typeof current === "object" && part in current) {
26
+ current = current[part];
27
+ } else {
28
+ console.warn(`⚠️ Could not resolve $ref: ${ref}`);
29
+ return null;
30
+ }
31
+ }
32
+
33
+ return current;
34
+ }
35
+
36
+ /**
37
+ * Generate a value for a primitive type with optional format
38
+ * @param {string} type - JSON Schema type
39
+ * @param {string} format - Optional format hint
40
+ * @param {object} schema - Full schema for additional constraints
41
+ * @returns {*} Generated value
42
+ */
43
+ function generateValue(type, format, schema = {}) {
44
+ // Handle enums first
45
+ if (schema.enum && schema.enum.length > 0) {
46
+ return faker.helpers.arrayElement(schema.enum);
47
+ }
48
+
49
+ // Handle examples
50
+ if (schema.example !== undefined) {
51
+ return schema.example;
52
+ }
53
+
54
+ // Handle default values
55
+ if (schema.default !== undefined) {
56
+ return schema.default;
57
+ }
58
+
59
+ switch (type) {
60
+ case "string":
61
+ return generateStringValue(format, schema);
62
+
63
+ case "integer":
64
+ return generateIntegerValue(schema);
65
+
66
+ case "number":
67
+ return generateNumberValue(schema);
68
+
69
+ case "boolean":
70
+ return faker.datatype.boolean();
71
+
72
+ case "null":
73
+ return null;
74
+
75
+ default:
76
+ return faker.lorem.word();
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Generate a string value based on format
82
+ * @param {string} format - String format
83
+ * @param {object} schema - Schema with constraints
84
+ * @returns {string} Generated string
85
+ */
86
+ function generateStringValue(format, schema) {
87
+ switch (format) {
88
+ case "email":
89
+ return faker.internet.email();
90
+
91
+ case "uuid":
92
+ return faker.string.uuid();
93
+
94
+ case "uri":
95
+ case "url":
96
+ return faker.internet.url();
97
+
98
+ case "hostname":
99
+ return faker.internet.domainName();
100
+
101
+ case "ipv4":
102
+ return faker.internet.ipv4();
103
+
104
+ case "ipv6":
105
+ return faker.internet.ipv6();
106
+
107
+ case "date":
108
+ return faker.date.recent().toISOString().split("T")[0];
109
+
110
+ case "date-time":
111
+ return faker.date.recent().toISOString();
112
+
113
+ case "time":
114
+ return faker.date.recent().toISOString().split("T")[1].split(".")[0];
115
+
116
+ case "password":
117
+ return faker.internet.password();
118
+
119
+ case "byte":
120
+ return Buffer.from(faker.lorem.word()).toString("base64");
121
+
122
+ case "binary":
123
+ return faker.string.alphanumeric(32);
124
+
125
+ case "phone":
126
+ return faker.phone.number();
127
+
128
+ case "color":
129
+ return faker.color.rgb();
130
+
131
+ default:
132
+ // Use property name hints if available
133
+ return generateStringByPattern(schema);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Generate string based on property name patterns
139
+ * @param {object} schema - Schema with property name
140
+ * @returns {string} Generated string
141
+ */
142
+ function generateStringByPattern(schema) {
143
+ const name = (schema.propertyName || "").toLowerCase();
144
+
145
+ // Common field name patterns
146
+ if (name.includes("name")) {
147
+ if (name.includes("first")) return faker.person.firstName();
148
+ if (name.includes("last")) return faker.person.lastName();
149
+ if (name.includes("full")) return faker.person.fullName();
150
+ if (name.includes("company")) return faker.company.name();
151
+ return faker.person.fullName();
152
+ }
153
+
154
+ if (name.includes("email")) return faker.internet.email();
155
+ if (name.includes("phone") || name.includes("mobile"))
156
+ return faker.phone.number();
157
+ if (name.includes("address")) return faker.location.streetAddress();
158
+ if (name.includes("city")) return faker.location.city();
159
+ if (name.includes("state")) return faker.location.state();
160
+ if (name.includes("country")) return faker.location.country();
161
+ if (name.includes("zip") || name.includes("postal"))
162
+ return faker.location.zipCode();
163
+ if (name.includes("url") || name.includes("link")) return faker.internet.url();
164
+ if (name.includes("image") || name.includes("avatar") || name.includes("photo"))
165
+ return faker.image.url();
166
+ if (name.includes("description") || name.includes("bio"))
167
+ return faker.lorem.paragraph();
168
+ if (name.includes("title")) return faker.lorem.sentence();
169
+ if (name.includes("slug")) return faker.helpers.slugify(faker.lorem.words(3));
170
+ if (name.includes("color")) return faker.color.rgb();
171
+ if (name.includes("token") || name.includes("key"))
172
+ return faker.string.alphanumeric(32);
173
+
174
+ // Apply length constraints
175
+ const minLength = schema.minLength || 1;
176
+ const maxLength = schema.maxLength || 50;
177
+ const length = faker.number.int({ min: minLength, max: maxLength });
178
+
179
+ if (schema.pattern) {
180
+ // Can't easily generate from regex, return simple string
181
+ return faker.string.alphanumeric(length);
182
+ }
183
+
184
+ return faker.lorem.words(Math.ceil(length / 6));
185
+ }
186
+
187
+ /**
188
+ * Generate an integer value with constraints
189
+ * @param {object} schema - Schema with constraints
190
+ * @returns {number} Generated integer
191
+ */
192
+ function generateIntegerValue(schema) {
193
+ const min = schema.minimum ?? 0;
194
+ const max = schema.maximum ?? 10000;
195
+ const exclusiveMin = schema.exclusiveMinimum ? 1 : 0;
196
+ const exclusiveMax = schema.exclusiveMaximum ? 1 : 0;
197
+
198
+ return faker.number.int({
199
+ min: min + exclusiveMin,
200
+ max: max - exclusiveMax,
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Generate a number value with constraints
206
+ * @param {object} schema - Schema with constraints
207
+ * @returns {number} Generated number
208
+ */
209
+ function generateNumberValue(schema) {
210
+ const min = schema.minimum ?? 0;
211
+ const max = schema.maximum ?? 10000;
212
+
213
+ return faker.number.float({
214
+ min,
215
+ max,
216
+ fractionDigits: 2,
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Generate data from a JSON Schema
222
+ * @param {object} schema - JSON Schema definition
223
+ * @param {object} spec - Full OpenAPI spec for $ref resolution
224
+ * @param {string} propertyName - Name of the property (for hints)
225
+ * @param {number} depth - Current recursion depth
226
+ * @returns {*} Generated data
227
+ */
228
+ function generateFromSchema(schema, spec = {}, propertyName = "", depth = 0) {
229
+ // Prevent infinite recursion
230
+ if (depth > 10) {
231
+ return null;
232
+ }
233
+
234
+ // Handle null/undefined schema
235
+ if (!schema) {
236
+ return null;
237
+ }
238
+
239
+ // Handle $ref
240
+ if (schema.$ref) {
241
+ const resolved = resolveRef(schema.$ref, spec);
242
+ if (resolved) {
243
+ return generateFromSchema(resolved, spec, propertyName, depth + 1);
244
+ }
245
+ return null;
246
+ }
247
+
248
+ // Handle allOf (merge schemas)
249
+ if (schema.allOf) {
250
+ const merged = {};
251
+ for (const subSchema of schema.allOf) {
252
+ const generated = generateFromSchema(subSchema, spec, propertyName, depth + 1);
253
+ if (generated && typeof generated === "object") {
254
+ Object.assign(merged, generated);
255
+ }
256
+ }
257
+ return merged;
258
+ }
259
+
260
+ // Handle oneOf/anyOf (pick first)
261
+ if (schema.oneOf && schema.oneOf.length > 0) {
262
+ return generateFromSchema(schema.oneOf[0], spec, propertyName, depth + 1);
263
+ }
264
+ if (schema.anyOf && schema.anyOf.length > 0) {
265
+ return generateFromSchema(schema.anyOf[0], spec, propertyName, depth + 1);
266
+ }
267
+
268
+ // Add property name hint to schema for string generation
269
+ const schemaWithHint = { ...schema, propertyName };
270
+
271
+ // Handle by type
272
+ const type = schema.type || "object";
273
+
274
+ if (type === "object" || schema.properties) {
275
+ return generateObject(schema, spec, depth);
276
+ }
277
+
278
+ if (type === "array") {
279
+ return generateArray(schema, spec, depth);
280
+ }
281
+
282
+ // Primitive types
283
+ return generateValue(type, schema.format, schemaWithHint);
284
+ }
285
+
286
+ /**
287
+ * Generate an object from schema
288
+ * @param {object} schema - Object schema
289
+ * @param {object} spec - Full spec for refs
290
+ * @param {number} depth - Recursion depth
291
+ * @returns {object} Generated object
292
+ */
293
+ function generateObject(schema, spec, depth) {
294
+ const result = {};
295
+
296
+ if (schema.properties) {
297
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
298
+ result[key] = generateFromSchema(propSchema, spec, key, depth + 1);
299
+ }
300
+ }
301
+
302
+ // Handle additionalProperties if no properties defined
303
+ if (!schema.properties && schema.additionalProperties) {
304
+ const count = faker.number.int({ min: 1, max: 3 });
305
+ for (let i = 0; i < count; i++) {
306
+ const key = faker.lorem.word();
307
+ result[key] = generateFromSchema(
308
+ schema.additionalProperties === true ? { type: "string" } : schema.additionalProperties,
309
+ spec,
310
+ key,
311
+ depth + 1
312
+ );
313
+ }
314
+ }
315
+
316
+ return result;
317
+ }
318
+
319
+ /**
320
+ * Generate an array from schema
321
+ * @param {object} schema - Array schema
322
+ * @param {object} spec - Full spec for refs
323
+ * @param {number} depth - Recursion depth
324
+ * @returns {array} Generated array
325
+ */
326
+ function generateArray(schema, spec, depth) {
327
+ const minItems = schema.minItems ?? 1;
328
+ const maxItems = schema.maxItems ?? 5;
329
+ const count = faker.number.int({ min: minItems, max: maxItems });
330
+
331
+ const items = [];
332
+ const itemSchema = schema.items || { type: "string" };
333
+
334
+ for (let i = 0; i < count; i++) {
335
+ items.push(generateFromSchema(itemSchema, spec, "", depth + 1));
336
+ }
337
+
338
+ return items;
339
+ }
340
+
341
+ /**
342
+ * Generate a response body from an OpenAPI response schema
343
+ * @param {object} responseSchema - Response schema from OpenAPI
344
+ * @param {object} spec - Full OpenAPI spec
345
+ * @returns {*} Generated response body
346
+ */
347
+ function generateResponse(responseSchema, spec) {
348
+ if (!responseSchema) {
349
+ return {};
350
+ }
351
+
352
+ // Handle content type (prefer JSON)
353
+ const content = responseSchema.content;
354
+ if (content) {
355
+ const jsonContent =
356
+ content["application/json"] ||
357
+ content["application/vnd.api+json"] ||
358
+ content["*/*"];
359
+
360
+ if (jsonContent && jsonContent.schema) {
361
+ return generateFromSchema(jsonContent.schema, spec);
362
+ }
363
+ }
364
+
365
+ // Direct schema (OpenAPI 2.0 style)
366
+ if (responseSchema.schema) {
367
+ return generateFromSchema(responseSchema.schema, spec);
368
+ }
369
+
370
+ return {};
371
+ }
372
+
373
+ /**
374
+ * Generate an error response
375
+ * @param {number} status - HTTP status code
376
+ * @param {string} message - Error message
377
+ * @returns {object} Error response object
378
+ */
379
+ function generateErrorResponse(status, message) {
380
+ return {
381
+ error: true,
382
+ status,
383
+ message,
384
+ timestamp: new Date().toISOString(),
385
+ };
386
+ }
387
+
388
+ module.exports = {
389
+ generateFromSchema,
390
+ generateResponse,
391
+ generateValue,
392
+ generateErrorResponse,
393
+ resolveRef,
394
+ };
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Route Generator
3
+ *
4
+ * Generates Express routes from OpenAPI path definitions.
5
+ * Supports x-mock extensions for delays, error simulation, and scenarios.
6
+ */
7
+
8
+ const express = require("express");
9
+ const { generateResponse, generateErrorResponse } = require("./response-generator");
10
+ const { createAuthMiddleware } = require("./auth-middleware");
11
+
12
+ // HTTP methods supported by Express
13
+ const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "options", "head"];
14
+
15
+ /**
16
+ * Convert OpenAPI path to Express path
17
+ * e.g., /events/{eventId}/checkin -> /events/:eventId/checkin
18
+ * @param {string} openApiPath - OpenAPI path with {param} syntax
19
+ * @returns {string} Express path with :param syntax
20
+ */
21
+ function convertPath(openApiPath) {
22
+ return openApiPath.replace(/\{([^}]+)\}/g, ":$1");
23
+ }
24
+
25
+ /**
26
+ * Get response schema for a given status code
27
+ * @param {object} operation - OpenAPI operation object
28
+ * @param {number} status - HTTP status code
29
+ * @returns {object|null} Response schema
30
+ */
31
+ function getResponseSchema(operation, status) {
32
+ if (!operation.responses) {
33
+ return null;
34
+ }
35
+
36
+ // Try exact status code
37
+ if (operation.responses[status]) {
38
+ return operation.responses[status];
39
+ }
40
+
41
+ // Try wildcard (2XX, 4XX, etc.)
42
+ const wildcard = `${Math.floor(status / 100)}XX`;
43
+ if (operation.responses[wildcard]) {
44
+ return operation.responses[wildcard];
45
+ }
46
+
47
+ // Try default
48
+ if (operation.responses.default) {
49
+ return operation.responses.default;
50
+ }
51
+
52
+ return null;
53
+ }
54
+
55
+ /**
56
+ * Get the default success status code for an operation
57
+ * @param {string} method - HTTP method
58
+ * @param {object} operation - OpenAPI operation
59
+ * @returns {number} Status code
60
+ */
61
+ function getSuccessStatus(method, operation) {
62
+ const responses = operation.responses || {};
63
+
64
+ // Check for explicit success codes
65
+ for (const code of ["200", "201", "202", "204"]) {
66
+ if (responses[code]) {
67
+ return parseInt(code);
68
+ }
69
+ }
70
+
71
+ // Default by method
72
+ if (method === "post") return 201;
73
+ if (method === "delete") return 204;
74
+ return 200;
75
+ }
76
+
77
+ /**
78
+ * Apply delay if configured
79
+ * @param {object} mockConfig - x-mock configuration
80
+ * @returns {Promise} Resolves after delay
81
+ */
82
+ function applyDelay(mockConfig) {
83
+ const delay = mockConfig.delay || parseInt(process.env.MOCK_API_DELAY) || 0;
84
+
85
+ if (delay > 0) {
86
+ return new Promise((resolve) => setTimeout(resolve, delay));
87
+ }
88
+
89
+ return Promise.resolve();
90
+ }
91
+
92
+ /**
93
+ * Determine if this request should simulate an error
94
+ * @param {object} mockConfig - x-mock configuration
95
+ * @returns {boolean} True if should error
96
+ */
97
+ function shouldSimulateError(mockConfig) {
98
+ const errorRate = mockConfig.errorRate || 0;
99
+
100
+ if (errorRate <= 0) return false;
101
+ if (errorRate >= 1) return true;
102
+
103
+ return Math.random() < errorRate;
104
+ }
105
+
106
+ /**
107
+ * Select a scenario based on weights
108
+ * @param {object} scenarios - Scenario definitions
109
+ * @returns {object|null} Selected scenario or null
110
+ */
111
+ function selectScenario(scenarios) {
112
+ if (!scenarios || typeof scenarios !== "object") {
113
+ return null;
114
+ }
115
+
116
+ const entries = Object.entries(scenarios);
117
+ if (entries.length === 0) return null;
118
+
119
+ // Calculate total weight
120
+ const totalWeight = entries.reduce(
121
+ (sum, [, config]) => sum + (config.weight || 1),
122
+ 0
123
+ );
124
+
125
+ // Random selection
126
+ let random = Math.random() * totalWeight;
127
+
128
+ for (const [name, config] of entries) {
129
+ const weight = config.weight || 1;
130
+ random -= weight;
131
+
132
+ if (random <= 0) {
133
+ return { name, ...config };
134
+ }
135
+ }
136
+
137
+ // Fallback to first scenario
138
+ const [name, config] = entries[0];
139
+ return { name, ...config };
140
+ }
141
+
142
+ /**
143
+ * Build a route handler for an OpenAPI operation
144
+ * @param {string} method - HTTP method
145
+ * @param {string} path - Original OpenAPI path
146
+ * @param {object} operation - OpenAPI operation object
147
+ * @param {object} spec - Full OpenAPI spec
148
+ * @param {object} options - Additional options (io for socket triggers)
149
+ * @returns {function} Express route handler
150
+ */
151
+ function buildRouteHandler(method, path, operation, spec, options = {}) {
152
+ const { io, socketTriggers } = options;
153
+
154
+ return async (req, res) => {
155
+ const mockConfig = operation["x-mock"] || {};
156
+ const operationId = operation.operationId || `${method.toUpperCase()} ${path}`;
157
+
158
+ console.log(`🎭 Mock API: ${operationId}`);
159
+
160
+ try {
161
+ // Apply delay
162
+ await applyDelay(mockConfig);
163
+
164
+ // Check for scenario mode
165
+ const scenario = selectScenario(mockConfig.scenarios);
166
+
167
+ // Determine if we should simulate an error
168
+ if (shouldSimulateError(mockConfig)) {
169
+ const errorSchema = getResponseSchema(operation, 500);
170
+ const errorBody = errorSchema
171
+ ? generateResponse(errorSchema, spec)
172
+ : generateErrorResponse(500, "Simulated server error");
173
+
174
+ console.log(` ⚠️ Simulated error (errorRate: ${mockConfig.errorRate})`);
175
+ return res.status(500).json(errorBody);
176
+ }
177
+
178
+ // Use scenario status if defined
179
+ const status = scenario?.status || getSuccessStatus(method, operation);
180
+
181
+ // Get response schema for this status
182
+ const responseSchema = getResponseSchema(operation, status);
183
+
184
+ // Generate response body
185
+ let body;
186
+ if (status === 204) {
187
+ // No content
188
+ body = undefined;
189
+ } else if (scenario?.response) {
190
+ // Use scenario-defined response
191
+ body = scenario.response;
192
+ } else if (responseSchema) {
193
+ body = generateResponse(responseSchema, spec);
194
+ } else {
195
+ body = { success: true };
196
+ }
197
+
198
+ // Log response info
199
+ if (scenario) {
200
+ console.log(` 📋 Scenario: ${scenario.name}`);
201
+ }
202
+ console.log(` ✅ Status: ${status}`);
203
+
204
+ // Send response
205
+ if (status === 204) {
206
+ res.status(204).end();
207
+ } else {
208
+ res.status(status).json(body);
209
+ }
210
+
211
+ // Trigger socket events if configured
212
+ if (io && socketTriggers) {
213
+ const { triggerSocketEvents } = require("./socket-triggers");
214
+ const operationKey = `${method.toUpperCase()} ${path}`;
215
+
216
+ triggerSocketEvents(io, socketTriggers, operationKey, {
217
+ request: {
218
+ method: req.method,
219
+ path: req.path,
220
+ params: req.params,
221
+ query: req.query,
222
+ body: req.body,
223
+ },
224
+ response: {
225
+ status,
226
+ body,
227
+ },
228
+ });
229
+ }
230
+ } catch (error) {
231
+ console.error(` ❌ Error: ${error.message}`);
232
+ res.status(500).json(generateErrorResponse(500, error.message));
233
+ }
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Generate Express routes from OpenAPI spec
239
+ * @param {object} openApiSpec - OpenAPI specification
240
+ * @param {object} options - Options (io for socket, socketTriggers map)
241
+ * @returns {express.Router} Express router with mock routes
242
+ */
243
+ function generateRoutes(openApiSpec, options = {}) {
244
+ const router = express.Router();
245
+
246
+ if (!openApiSpec || !openApiSpec.paths) {
247
+ console.warn("⚠️ No paths found in OpenAPI spec");
248
+ return router;
249
+ }
250
+
251
+ let routeCount = 0;
252
+
253
+ // Iterate over all paths
254
+ for (const [path, pathItem] of Object.entries(openApiSpec.paths)) {
255
+ const expressPath = convertPath(path);
256
+
257
+ // Iterate over HTTP methods
258
+ for (const method of HTTP_METHODS) {
259
+ const operation = pathItem[method];
260
+
261
+ if (!operation) continue;
262
+
263
+ // Create auth middleware for this operation
264
+ const authMiddleware = createAuthMiddleware(operation);
265
+
266
+ // Create route handler
267
+ const handler = buildRouteHandler(method, path, operation, openApiSpec, options);
268
+
269
+ // Register route
270
+ router[method](expressPath, authMiddleware, handler);
271
+ routeCount++;
272
+
273
+ const operationId = operation.operationId || `${method.toUpperCase()} ${path}`;
274
+ console.log(` 📍 ${method.toUpperCase()} ${expressPath} (${operationId})`);
275
+ }
276
+ }
277
+
278
+ console.log(`✅ Generated ${routeCount} mock routes`);
279
+
280
+ return router;
281
+ }
282
+
283
+ /**
284
+ * Get route statistics from OpenAPI spec
285
+ * @param {object} openApiSpec - OpenAPI specification
286
+ * @returns {object} Statistics object
287
+ */
288
+ function getRouteStats(openApiSpec) {
289
+ if (!openApiSpec || !openApiSpec.paths) {
290
+ return { total: 0, byMethod: {} };
291
+ }
292
+
293
+ const stats = {
294
+ total: 0,
295
+ byMethod: {},
296
+ paths: [],
297
+ };
298
+
299
+ for (const [path, pathItem] of Object.entries(openApiSpec.paths)) {
300
+ for (const method of HTTP_METHODS) {
301
+ if (pathItem[method]) {
302
+ stats.total++;
303
+ stats.byMethod[method] = (stats.byMethod[method] || 0) + 1;
304
+ stats.paths.push({
305
+ method: method.toUpperCase(),
306
+ path,
307
+ operationId: pathItem[method].operationId,
308
+ });
309
+ }
310
+ }
311
+ }
312
+
313
+ return stats;
314
+ }
315
+
316
+ module.exports = {
317
+ generateRoutes,
318
+ convertPath,
319
+ getResponseSchema,
320
+ getSuccessStatus,
321
+ buildRouteHandler,
322
+ getRouteStats,
323
+ };