@gxp-dev/tools 2.0.6 → 2.0.8
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/bin/lib/commands/build.js +18 -12
- package/browser-extensions/README.md +1 -0
- package/browser-extensions/chrome/background.js +857 -0
- package/browser-extensions/chrome/content.js +51 -0
- package/browser-extensions/chrome/devtools.html +9 -0
- package/browser-extensions/chrome/devtools.js +23 -0
- package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
- package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
- package/browser-extensions/chrome/inspector.js +1087 -0
- package/browser-extensions/chrome/manifest.json +70 -0
- package/browser-extensions/chrome/panel.html +638 -0
- package/browser-extensions/chrome/panel.js +862 -0
- package/browser-extensions/chrome/popup.html +399 -0
- package/browser-extensions/chrome/popup.js +515 -0
- package/browser-extensions/chrome/rules.json +1 -0
- package/browser-extensions/chrome/test-chrome.html +145 -0
- package/browser-extensions/chrome/test-mixed-content.html +190 -0
- package/browser-extensions/chrome/test-uri-pattern.html +199 -0
- package/browser-extensions/firefox/README.md +134 -0
- package/browser-extensions/firefox/background.js +804 -0
- package/browser-extensions/firefox/content.js +120 -0
- package/browser-extensions/firefox/debug-errors.html +229 -0
- package/browser-extensions/firefox/debug-https.html +113 -0
- package/browser-extensions/firefox/devtools.html +9 -0
- package/browser-extensions/firefox/devtools.js +24 -0
- package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
- package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
- package/browser-extensions/firefox/inspector.js +1087 -0
- package/browser-extensions/firefox/manifest.json +67 -0
- package/browser-extensions/firefox/panel.html +638 -0
- package/browser-extensions/firefox/panel.js +862 -0
- package/browser-extensions/firefox/popup.html +525 -0
- package/browser-extensions/firefox/popup.js +536 -0
- package/browser-extensions/firefox/test-gramercy.html +126 -0
- package/browser-extensions/firefox/test-imports.html +58 -0
- package/browser-extensions/firefox/test-masking.html +147 -0
- package/browser-extensions/firefox/test-uri-pattern.html +199 -0
- package/package.json +7 -2
- package/runtime/PortalContainer.vue +326 -0
- package/runtime/dev-tools/DevToolsModal.vue +217 -0
- package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
- package/runtime/dev-tools/MockDataEditor.vue +621 -0
- package/runtime/dev-tools/SocketSimulator.vue +562 -0
- package/runtime/dev-tools/StoreInspector.vue +644 -0
- package/runtime/dev-tools/index.js +6 -0
- package/runtime/gxpStringsPlugin.js +428 -0
- package/runtime/index.html +22 -0
- package/runtime/main.js +32 -0
- package/runtime/mock-api/auth-middleware.js +97 -0
- package/runtime/mock-api/image-generator.js +221 -0
- package/runtime/mock-api/index.js +197 -0
- package/runtime/mock-api/response-generator.js +394 -0
- package/runtime/mock-api/route-generator.js +323 -0
- package/runtime/mock-api/socket-triggers.js +371 -0
- package/runtime/mock-api/spec-loader.js +300 -0
- package/runtime/server.js +180 -0
- package/runtime/stores/gxpPortalConfigStore.js +554 -0
- package/runtime/stores/index.js +6 -0
- package/runtime/vite-inspector-plugin.js +749 -0
- package/runtime/vite-source-tracker-plugin.js +232 -0
- package/runtime/vite.config.js +402 -0
- package/scripts/launch-chrome.js +90 -0
- package/scripts/pack-chrome.js +91 -0
- package/socket-events/AiSessionMessageCreated.json +18 -0
- package/socket-events/SocialStreamPostCreated.json +24 -0
- package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
- package/template/README.md +332 -0
- package/template/app-manifest.json +32 -0
- package/template/dev-assets/images/avatar-placeholder.png +0 -0
- package/template/dev-assets/images/background-placeholder.jpg +0 -0
- package/template/dev-assets/images/banner-placeholder.jpg +0 -0
- package/template/dev-assets/images/icon-placeholder.png +0 -0
- package/template/dev-assets/images/logo-placeholder.png +0 -0
- package/template/dev-assets/images/product-placeholder.jpg +0 -0
- package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
- package/template/env.example +51 -0
- package/template/gitignore +53 -0
- package/template/index.html +22 -0
- package/template/main.js +28 -0
- package/template/src/DemoPage.vue +459 -0
- package/template/src/Plugin.vue +38 -0
- package/template/src/stores/index.js +9 -0
- package/template/src/stores/test-data.json +173 -0
- package/template/theme-layouts/AdditionalStyling.css +0 -0
- package/template/theme-layouts/PrivateLayout.vue +39 -0
- package/template/theme-layouts/PublicLayout.vue +39 -0
- package/template/theme-layouts/SystemLayout.vue +39 -0
- 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
|
+
};
|