@gxp-dev/tools 2.0.5
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/.github/workflows/npm-publish.yml +48 -0
- package/CLAUDE.md +400 -0
- package/README.md +247 -0
- package/REFACTOR_PLAN.md +194 -0
- package/bin/gx-devtools.js +87 -0
- package/bin/lib/cli.js +251 -0
- package/bin/lib/commands/assets.js +337 -0
- package/bin/lib/commands/build.js +259 -0
- package/bin/lib/commands/datastore.js +433 -0
- package/bin/lib/commands/dev.js +328 -0
- package/bin/lib/commands/extensions.js +298 -0
- package/bin/lib/commands/index.js +35 -0
- package/bin/lib/commands/init.js +307 -0
- package/bin/lib/commands/publish.js +189 -0
- package/bin/lib/commands/socket.js +158 -0
- package/bin/lib/commands/ssl.js +47 -0
- package/bin/lib/constants.js +120 -0
- package/bin/lib/tui/App.tsx +600 -0
- package/bin/lib/tui/components/CommandInput.tsx +278 -0
- package/bin/lib/tui/components/GeminiPanel.tsx +161 -0
- package/bin/lib/tui/components/Header.tsx +27 -0
- package/bin/lib/tui/components/LogPanel.tsx +122 -0
- package/bin/lib/tui/components/TabBar.tsx +56 -0
- package/bin/lib/tui/components/WelcomeScreen.tsx +80 -0
- package/bin/lib/tui/index.tsx +63 -0
- package/bin/lib/tui/services/ExtensionService.ts +122 -0
- package/bin/lib/tui/services/GeminiService.ts +395 -0
- package/bin/lib/tui/services/ServiceManager.ts +336 -0
- package/bin/lib/tui/services/SocketService.ts +204 -0
- package/bin/lib/tui/services/ViteService.ts +107 -0
- package/bin/lib/tui/services/index.ts +13 -0
- package/bin/lib/utils/files.js +180 -0
- package/bin/lib/utils/index.js +17 -0
- package/bin/lib/utils/paths.js +138 -0
- package/bin/lib/utils/prompts.js +71 -0
- package/bin/lib/utils/ssl.js +233 -0
- 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/docs/DOCUSAURUS_IMPORT.md +378 -0
- package/docs/_category_.json +8 -0
- package/docs/app-manifest.md +272 -0
- package/docs/building-for-platform.md +315 -0
- package/docs/dev-tools.md +291 -0
- package/docs/getting-started.md +180 -0
- package/docs/gxp-store.md +305 -0
- package/docs/index.md +44 -0
- package/package.json +77 -0
- 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
- package/tsconfig.tui.json +21 -0
- package/vite.config.js +164 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket Triggers
|
|
3
|
+
*
|
|
4
|
+
* Parses AsyncAPI specs for x-triggered-by extensions and emits
|
|
5
|
+
* Socket.IO events after matching API calls complete.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse AsyncAPI spec for socket triggers
|
|
10
|
+
* @param {object} asyncApiSpec - AsyncAPI specification
|
|
11
|
+
* @returns {object} Map of operation keys to trigger definitions
|
|
12
|
+
*/
|
|
13
|
+
function parseSocketTriggers(asyncApiSpec) {
|
|
14
|
+
const triggers = {};
|
|
15
|
+
|
|
16
|
+
if (!asyncApiSpec) {
|
|
17
|
+
return triggers;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Handle AsyncAPI 2.x format
|
|
21
|
+
if (asyncApiSpec.channels) {
|
|
22
|
+
parseChannels(asyncApiSpec.channels, asyncApiSpec, triggers);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Handle AsyncAPI 3.x format
|
|
26
|
+
if (asyncApiSpec.operations) {
|
|
27
|
+
parseOperations(asyncApiSpec.operations, asyncApiSpec, triggers);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Also check components/messages directly
|
|
31
|
+
if (asyncApiSpec.components?.messages) {
|
|
32
|
+
parseMessages(asyncApiSpec.components.messages, triggers);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const triggerCount = Object.keys(triggers).length;
|
|
36
|
+
if (triggerCount > 0) {
|
|
37
|
+
console.log(`📡 Parsed ${triggerCount} socket trigger definitions`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return triggers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse channels (AsyncAPI 2.x)
|
|
45
|
+
* @param {object} channels - Channels object
|
|
46
|
+
* @param {object} spec - Full spec
|
|
47
|
+
* @param {object} triggers - Triggers map to populate
|
|
48
|
+
*/
|
|
49
|
+
function parseChannels(channels, spec, triggers) {
|
|
50
|
+
for (const [channelName, channel] of Object.entries(channels)) {
|
|
51
|
+
// Check publish/subscribe operations
|
|
52
|
+
const operations = [
|
|
53
|
+
channel.publish,
|
|
54
|
+
channel.subscribe,
|
|
55
|
+
].filter(Boolean);
|
|
56
|
+
|
|
57
|
+
for (const operation of operations) {
|
|
58
|
+
const message = operation.message;
|
|
59
|
+
if (message) {
|
|
60
|
+
extractTriggersFromMessage(channelName, message, spec, triggers);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse operations (AsyncAPI 3.x)
|
|
68
|
+
* @param {object} operations - Operations object
|
|
69
|
+
* @param {object} spec - Full spec
|
|
70
|
+
* @param {object} triggers - Triggers map to populate
|
|
71
|
+
*/
|
|
72
|
+
function parseOperations(operations, spec, triggers) {
|
|
73
|
+
for (const [, operation] of Object.entries(operations)) {
|
|
74
|
+
if (operation.messages) {
|
|
75
|
+
for (const message of operation.messages) {
|
|
76
|
+
const channelName = operation.channel?.$ref || "default";
|
|
77
|
+
extractTriggersFromMessage(channelName, message, spec, triggers);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parse messages directly from components
|
|
85
|
+
* @param {object} messages - Messages object
|
|
86
|
+
* @param {object} triggers - Triggers map to populate
|
|
87
|
+
*/
|
|
88
|
+
function parseMessages(messages, triggers) {
|
|
89
|
+
for (const [messageName, message] of Object.entries(messages)) {
|
|
90
|
+
if (message["x-triggered-by"]) {
|
|
91
|
+
const triggerDefs = Array.isArray(message["x-triggered-by"])
|
|
92
|
+
? message["x-triggered-by"]
|
|
93
|
+
: [message["x-triggered-by"]];
|
|
94
|
+
|
|
95
|
+
for (const triggerDef of triggerDefs) {
|
|
96
|
+
const operationKey = triggerDef.operation;
|
|
97
|
+
if (!operationKey) continue;
|
|
98
|
+
|
|
99
|
+
if (!triggers[operationKey]) {
|
|
100
|
+
triggers[operationKey] = [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
triggers[operationKey].push({
|
|
104
|
+
event: message.name || messageName,
|
|
105
|
+
channel: triggerDef.channel,
|
|
106
|
+
delay: triggerDef.delay || 0,
|
|
107
|
+
condition: triggerDef.condition,
|
|
108
|
+
payload: triggerDef.payload || message.payload,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Extract triggers from a message definition
|
|
117
|
+
* @param {string} channelName - Channel name
|
|
118
|
+
* @param {object} message - Message object
|
|
119
|
+
* @param {object} spec - Full spec for $ref resolution
|
|
120
|
+
* @param {object} triggers - Triggers map to populate
|
|
121
|
+
*/
|
|
122
|
+
function extractTriggersFromMessage(channelName, message, spec, triggers) {
|
|
123
|
+
// Resolve $ref if needed
|
|
124
|
+
let resolvedMessage = message;
|
|
125
|
+
if (message.$ref) {
|
|
126
|
+
resolvedMessage = resolveRef(message.$ref, spec) || message;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check for x-triggered-by extension
|
|
130
|
+
const triggerDefs = resolvedMessage["x-triggered-by"];
|
|
131
|
+
if (!triggerDefs) return;
|
|
132
|
+
|
|
133
|
+
const triggerArray = Array.isArray(triggerDefs) ? triggerDefs : [triggerDefs];
|
|
134
|
+
|
|
135
|
+
for (const triggerDef of triggerArray) {
|
|
136
|
+
const operationKey = triggerDef.operation;
|
|
137
|
+
if (!operationKey) continue;
|
|
138
|
+
|
|
139
|
+
if (!triggers[operationKey]) {
|
|
140
|
+
triggers[operationKey] = [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
triggers[operationKey].push({
|
|
144
|
+
event: resolvedMessage.name || resolvedMessage.messageId || "unknown",
|
|
145
|
+
channel: triggerDef.channel || channelName,
|
|
146
|
+
delay: triggerDef.delay || 0,
|
|
147
|
+
condition: triggerDef.condition,
|
|
148
|
+
payload: triggerDef.payload || resolvedMessage.payload,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Resolve a $ref reference
|
|
155
|
+
* @param {string} ref - Reference string
|
|
156
|
+
* @param {object} spec - Full spec
|
|
157
|
+
* @returns {object|null} Resolved object
|
|
158
|
+
*/
|
|
159
|
+
function resolveRef(ref, spec) {
|
|
160
|
+
if (!ref || !ref.startsWith("#/")) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const parts = ref.slice(2).split("/");
|
|
165
|
+
let current = spec;
|
|
166
|
+
|
|
167
|
+
for (const part of parts) {
|
|
168
|
+
if (current && typeof current === "object" && part in current) {
|
|
169
|
+
current = current[part];
|
|
170
|
+
} else {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return current;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Template a payload with request/response data
|
|
180
|
+
* @param {object} payload - Payload template
|
|
181
|
+
* @param {object} context - Context with request and response data
|
|
182
|
+
* @returns {object} Templated payload
|
|
183
|
+
*/
|
|
184
|
+
function templatePayload(payload, context) {
|
|
185
|
+
if (!payload) return {};
|
|
186
|
+
|
|
187
|
+
const payloadStr = JSON.stringify(payload);
|
|
188
|
+
|
|
189
|
+
// Replace template variables
|
|
190
|
+
const templated = payloadStr.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
|
191
|
+
const value = resolvePath(path.trim(), context);
|
|
192
|
+
|
|
193
|
+
if (value === undefined) {
|
|
194
|
+
return match; // Keep original if not found
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle different types
|
|
198
|
+
if (typeof value === "string") {
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return JSON.stringify(value);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(templated);
|
|
207
|
+
} catch {
|
|
208
|
+
// If JSON parse fails, return original
|
|
209
|
+
return payload;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Resolve a dot-notation path in an object
|
|
215
|
+
* @param {string} path - Dot-notation path (e.g., "response.body.id")
|
|
216
|
+
* @param {object} obj - Object to resolve in
|
|
217
|
+
* @returns {*} Resolved value
|
|
218
|
+
*/
|
|
219
|
+
function resolvePath(path, obj) {
|
|
220
|
+
// Handle special values
|
|
221
|
+
if (path === "now") {
|
|
222
|
+
return new Date().toISOString();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (path === "timestamp") {
|
|
226
|
+
return Date.now();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const parts = path.split(".");
|
|
230
|
+
let current = obj;
|
|
231
|
+
|
|
232
|
+
for (const part of parts) {
|
|
233
|
+
if (current && typeof current === "object" && part in current) {
|
|
234
|
+
current = current[part];
|
|
235
|
+
} else {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return current;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Evaluate a condition expression
|
|
245
|
+
* @param {string} condition - Condition expression
|
|
246
|
+
* @param {object} context - Context with request and response data
|
|
247
|
+
* @returns {boolean} Condition result
|
|
248
|
+
*/
|
|
249
|
+
function evaluateCondition(condition, context) {
|
|
250
|
+
if (!condition) return true;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// Simple condition parsing (e.g., "response.status == 200")
|
|
254
|
+
const match = condition.match(/^([^\s]+)\s*(==|!=|>|<|>=|<=)\s*(.+)$/);
|
|
255
|
+
|
|
256
|
+
if (!match) return true;
|
|
257
|
+
|
|
258
|
+
const [, path, operator, valueStr] = match;
|
|
259
|
+
const actualValue = resolvePath(path, context);
|
|
260
|
+
|
|
261
|
+
// Parse the expected value
|
|
262
|
+
let expectedValue;
|
|
263
|
+
try {
|
|
264
|
+
expectedValue = JSON.parse(valueStr);
|
|
265
|
+
} catch {
|
|
266
|
+
expectedValue = valueStr;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
switch (operator) {
|
|
270
|
+
case "==":
|
|
271
|
+
return actualValue == expectedValue;
|
|
272
|
+
case "!=":
|
|
273
|
+
return actualValue != expectedValue;
|
|
274
|
+
case ">":
|
|
275
|
+
return actualValue > expectedValue;
|
|
276
|
+
case "<":
|
|
277
|
+
return actualValue < expectedValue;
|
|
278
|
+
case ">=":
|
|
279
|
+
return actualValue >= expectedValue;
|
|
280
|
+
case "<=":
|
|
281
|
+
return actualValue <= expectedValue;
|
|
282
|
+
default:
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Trigger socket events for a completed API operation
|
|
292
|
+
* @param {object} io - Socket.IO server instance
|
|
293
|
+
* @param {object} socketTriggers - Parsed trigger definitions
|
|
294
|
+
* @param {string} operationKey - Operation key (e.g., "POST /events/{eventId}/checkin")
|
|
295
|
+
* @param {object} context - Request/response context
|
|
296
|
+
*/
|
|
297
|
+
function triggerSocketEvents(io, socketTriggers, operationKey, context) {
|
|
298
|
+
if (!io || !socketTriggers) return;
|
|
299
|
+
|
|
300
|
+
const triggers = socketTriggers[operationKey];
|
|
301
|
+
if (!triggers || triggers.length === 0) return;
|
|
302
|
+
|
|
303
|
+
for (const trigger of triggers) {
|
|
304
|
+
// Evaluate condition
|
|
305
|
+
if (!evaluateCondition(trigger.condition, context)) {
|
|
306
|
+
console.log(` ⏭️ Skipped socket event (condition not met): ${trigger.event}`);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Template the payload
|
|
311
|
+
const payload = templatePayload(trigger.payload, context);
|
|
312
|
+
|
|
313
|
+
// Template the channel name
|
|
314
|
+
let channel = trigger.channel || "";
|
|
315
|
+
channel = channel.replace(/\{([^}]+)\}/g, (match, path) => {
|
|
316
|
+
const value = resolvePath(`request.params.${path}`, context);
|
|
317
|
+
return value !== undefined ? value : match;
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Schedule the emit
|
|
321
|
+
const delay = trigger.delay || 0;
|
|
322
|
+
|
|
323
|
+
const emit = () => {
|
|
324
|
+
console.log(` 📡 Emitting socket event: ${trigger.event}`);
|
|
325
|
+
if (channel) {
|
|
326
|
+
console.log(` Channel: ${channel}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
io.emit(trigger.event, payload);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (delay > 0) {
|
|
333
|
+
console.log(` ⏱️ Scheduling socket event: ${trigger.event} (${delay}ms delay)`);
|
|
334
|
+
setTimeout(emit, delay);
|
|
335
|
+
} else {
|
|
336
|
+
emit();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get trigger statistics
|
|
343
|
+
* @param {object} socketTriggers - Parsed trigger definitions
|
|
344
|
+
* @returns {object} Statistics
|
|
345
|
+
*/
|
|
346
|
+
function getTriggerStats(socketTriggers) {
|
|
347
|
+
if (!socketTriggers) {
|
|
348
|
+
return { total: 0, operations: [] };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const operations = [];
|
|
352
|
+
let total = 0;
|
|
353
|
+
|
|
354
|
+
for (const [operation, triggers] of Object.entries(socketTriggers)) {
|
|
355
|
+
total += triggers.length;
|
|
356
|
+
operations.push({
|
|
357
|
+
operation,
|
|
358
|
+
events: triggers.map((t) => t.event),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { total, operations };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports = {
|
|
366
|
+
parseSocketTriggers,
|
|
367
|
+
triggerSocketEvents,
|
|
368
|
+
templatePayload,
|
|
369
|
+
evaluateCondition,
|
|
370
|
+
getTriggerStats,
|
|
371
|
+
};
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec Loader
|
|
3
|
+
*
|
|
4
|
+
* Fetches and caches OpenAPI, AsyncAPI, and Webhook specs from the platform.
|
|
5
|
+
* Falls back to local files when the platform API is unreachable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const axios = require("axios");
|
|
11
|
+
const https = require("https");
|
|
12
|
+
|
|
13
|
+
// Import environment URLs from constants
|
|
14
|
+
let ENVIRONMENT_URLS;
|
|
15
|
+
try {
|
|
16
|
+
// When running from node_modules
|
|
17
|
+
ENVIRONMENT_URLS = require("../../bin/lib/constants").ENVIRONMENT_URLS;
|
|
18
|
+
} catch {
|
|
19
|
+
// Fallback for direct execution
|
|
20
|
+
ENVIRONMENT_URLS = require(path.join(
|
|
21
|
+
__dirname,
|
|
22
|
+
"../../bin/lib/constants"
|
|
23
|
+
)).ENVIRONMENT_URLS;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Spec cache
|
|
27
|
+
const cache = {
|
|
28
|
+
openApi: null,
|
|
29
|
+
asyncApi: null,
|
|
30
|
+
webhooks: null,
|
|
31
|
+
lastFetch: null,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Default cache TTL (5 minutes)
|
|
35
|
+
const DEFAULT_CACHE_TTL = 5 * 60 * 1000;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get cache TTL from environment or use default
|
|
39
|
+
*/
|
|
40
|
+
function getCacheTTL() {
|
|
41
|
+
return parseInt(process.env.MOCK_API_CACHE_TTL) || DEFAULT_CACHE_TTL;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get environment configuration based on API_ENV
|
|
46
|
+
* @param {string} env - Environment name (defaults to 'production')
|
|
47
|
+
* @returns {object} Environment URLs configuration
|
|
48
|
+
*/
|
|
49
|
+
function getEnvironmentConfig(env) {
|
|
50
|
+
const envName = env || process.env.API_ENV || "production";
|
|
51
|
+
const config = ENVIRONMENT_URLS[envName];
|
|
52
|
+
|
|
53
|
+
if (!config) {
|
|
54
|
+
console.warn(
|
|
55
|
+
`⚠️ Unknown environment "${envName}", falling back to production`
|
|
56
|
+
);
|
|
57
|
+
return ENVIRONMENT_URLS.production;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create axios instance with SSL handling for local development
|
|
65
|
+
*/
|
|
66
|
+
function createHttpClient() {
|
|
67
|
+
return axios.create({
|
|
68
|
+
timeout: 10000,
|
|
69
|
+
httpsAgent: new https.Agent({
|
|
70
|
+
rejectUnauthorized: process.env.NODE_ENV === "production",
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch a spec from a URL
|
|
77
|
+
* @param {string} url - URL to fetch
|
|
78
|
+
* @param {string} specName - Name for logging
|
|
79
|
+
* @returns {object|null} Parsed JSON spec or null on error
|
|
80
|
+
*/
|
|
81
|
+
async function fetchSpec(url, specName) {
|
|
82
|
+
const client = createHttpClient();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
console.log(`📡 Fetching ${specName} from ${url}`);
|
|
86
|
+
const response = await client.get(url);
|
|
87
|
+
console.log(`✅ ${specName} loaded successfully`);
|
|
88
|
+
return response.data;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const message = error.response
|
|
91
|
+
? `HTTP ${error.response.status}`
|
|
92
|
+
: error.message;
|
|
93
|
+
console.warn(`⚠️ Failed to fetch ${specName}: ${message}`);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Load a spec from a local file
|
|
100
|
+
* @param {string} projectRoot - Project root directory
|
|
101
|
+
* @param {string} filename - Filename to load
|
|
102
|
+
* @param {string} specName - Name for logging
|
|
103
|
+
* @returns {object|null} Parsed JSON spec or null if not found
|
|
104
|
+
*/
|
|
105
|
+
function loadLocalSpec(projectRoot, filename, specName) {
|
|
106
|
+
const filePath = path.join(projectRoot, filename);
|
|
107
|
+
|
|
108
|
+
if (!fs.existsSync(filePath)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
console.log(`📁 Loading local ${specName} from ${filename}`);
|
|
114
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
115
|
+
const spec = JSON.parse(content);
|
|
116
|
+
console.log(`✅ Local ${specName} loaded successfully`);
|
|
117
|
+
return spec;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.warn(`⚠️ Failed to parse local ${specName}: ${error.message}`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Fetch OpenAPI spec from platform or local file
|
|
126
|
+
* @param {string} projectRoot - Project root for local fallback
|
|
127
|
+
* @returns {object|null} OpenAPI spec
|
|
128
|
+
*/
|
|
129
|
+
async function fetchOpenApiSpec(projectRoot) {
|
|
130
|
+
const config = getEnvironmentConfig();
|
|
131
|
+
|
|
132
|
+
// Try remote first
|
|
133
|
+
let spec = await fetchSpec(config.openApiSpec, "OpenAPI spec");
|
|
134
|
+
|
|
135
|
+
// Fallback to local
|
|
136
|
+
if (!spec && projectRoot) {
|
|
137
|
+
spec = loadLocalSpec(projectRoot, "openapi.json", "OpenAPI spec");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return spec;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Fetch AsyncAPI spec from platform or local file
|
|
145
|
+
* @param {string} projectRoot - Project root for local fallback
|
|
146
|
+
* @returns {object|null} AsyncAPI spec
|
|
147
|
+
*/
|
|
148
|
+
async function fetchAsyncApiSpec(projectRoot) {
|
|
149
|
+
const config = getEnvironmentConfig();
|
|
150
|
+
|
|
151
|
+
// Try remote first
|
|
152
|
+
let spec = await fetchSpec(config.asyncApiSpec, "AsyncAPI spec");
|
|
153
|
+
|
|
154
|
+
// Fallback to local
|
|
155
|
+
if (!spec && projectRoot) {
|
|
156
|
+
spec = loadLocalSpec(projectRoot, "asyncapi.json", "AsyncAPI spec");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return spec;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Fetch Webhook spec from platform or local file
|
|
164
|
+
* @param {string} projectRoot - Project root for local fallback
|
|
165
|
+
* @returns {object|null} Webhook spec
|
|
166
|
+
*/
|
|
167
|
+
async function fetchWebhookSpec(projectRoot) {
|
|
168
|
+
const config = getEnvironmentConfig();
|
|
169
|
+
|
|
170
|
+
// Try remote first
|
|
171
|
+
let spec = await fetchSpec(config.webhookSpec, "Webhook spec");
|
|
172
|
+
|
|
173
|
+
// Fallback to local
|
|
174
|
+
if (!spec && projectRoot) {
|
|
175
|
+
spec = loadLocalSpec(projectRoot, "webhooks.json", "Webhook spec");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return spec;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if cache is still valid
|
|
183
|
+
* @returns {boolean} True if cache is valid
|
|
184
|
+
*/
|
|
185
|
+
function isCacheValid() {
|
|
186
|
+
if (!cache.lastFetch) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const elapsed = Date.now() - cache.lastFetch;
|
|
191
|
+
return elapsed < getCacheTTL();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Load all specs with caching
|
|
196
|
+
* @param {string} projectRoot - Project root for local fallback
|
|
197
|
+
* @param {boolean} forceRefresh - Force refresh even if cache is valid
|
|
198
|
+
* @returns {object} Object containing all specs
|
|
199
|
+
*/
|
|
200
|
+
async function loadSpecs(projectRoot, forceRefresh = false) {
|
|
201
|
+
// Return cached if valid and not forcing refresh
|
|
202
|
+
if (!forceRefresh && isCacheValid()) {
|
|
203
|
+
console.log("📦 Using cached specs");
|
|
204
|
+
return {
|
|
205
|
+
openApi: cache.openApi,
|
|
206
|
+
asyncApi: cache.asyncApi,
|
|
207
|
+
webhooks: cache.webhooks,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const root = projectRoot || process.cwd();
|
|
212
|
+
|
|
213
|
+
console.log("🔄 Loading API specs...");
|
|
214
|
+
console.log(` Environment: ${process.env.API_ENV || "production"}`);
|
|
215
|
+
|
|
216
|
+
// Fetch all specs in parallel
|
|
217
|
+
const [openApi, asyncApi, webhooks] = await Promise.all([
|
|
218
|
+
fetchOpenApiSpec(root),
|
|
219
|
+
fetchAsyncApiSpec(root),
|
|
220
|
+
fetchWebhookSpec(root),
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
// Update cache
|
|
224
|
+
cache.openApi = openApi;
|
|
225
|
+
cache.asyncApi = asyncApi;
|
|
226
|
+
cache.webhooks = webhooks;
|
|
227
|
+
cache.lastFetch = Date.now();
|
|
228
|
+
|
|
229
|
+
// Log summary
|
|
230
|
+
const loaded = [];
|
|
231
|
+
if (openApi) loaded.push("OpenAPI");
|
|
232
|
+
if (asyncApi) loaded.push("AsyncAPI");
|
|
233
|
+
if (webhooks) loaded.push("Webhooks");
|
|
234
|
+
|
|
235
|
+
if (loaded.length > 0) {
|
|
236
|
+
console.log(`✅ Loaded specs: ${loaded.join(", ")}`);
|
|
237
|
+
} else {
|
|
238
|
+
console.warn("⚠️ No specs were loaded");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { openApi, asyncApi, webhooks };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Clear the spec cache and refetch
|
|
246
|
+
* @param {string} projectRoot - Project root for local fallback
|
|
247
|
+
* @returns {object} Freshly loaded specs
|
|
248
|
+
*/
|
|
249
|
+
async function refreshSpecs(projectRoot) {
|
|
250
|
+
console.log("🔄 Refreshing API specs...");
|
|
251
|
+
cache.openApi = null;
|
|
252
|
+
cache.asyncApi = null;
|
|
253
|
+
cache.webhooks = null;
|
|
254
|
+
cache.lastFetch = null;
|
|
255
|
+
|
|
256
|
+
return loadSpecs(projectRoot, true);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get current cache status
|
|
261
|
+
* @returns {object} Cache status info
|
|
262
|
+
*/
|
|
263
|
+
function getCacheStatus() {
|
|
264
|
+
const ttl = getCacheTTL();
|
|
265
|
+
const elapsed = cache.lastFetch ? Date.now() - cache.lastFetch : null;
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
hasOpenApi: !!cache.openApi,
|
|
269
|
+
hasAsyncApi: !!cache.asyncApi,
|
|
270
|
+
hasWebhooks: !!cache.webhooks,
|
|
271
|
+
lastFetch: cache.lastFetch ? new Date(cache.lastFetch).toISOString() : null,
|
|
272
|
+
cacheValid: isCacheValid(),
|
|
273
|
+
ttlMs: ttl,
|
|
274
|
+
expiresIn: elapsed !== null ? Math.max(0, ttl - elapsed) : null,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get the raw cached specs (for direct access)
|
|
280
|
+
* @returns {object} Cached specs
|
|
281
|
+
*/
|
|
282
|
+
function getCachedSpecs() {
|
|
283
|
+
return {
|
|
284
|
+
openApi: cache.openApi,
|
|
285
|
+
asyncApi: cache.asyncApi,
|
|
286
|
+
webhooks: cache.webhooks,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = {
|
|
291
|
+
getEnvironmentConfig,
|
|
292
|
+
fetchOpenApiSpec,
|
|
293
|
+
fetchAsyncApiSpec,
|
|
294
|
+
fetchWebhookSpec,
|
|
295
|
+
loadSpecs,
|
|
296
|
+
refreshSpecs,
|
|
297
|
+
getCacheStatus,
|
|
298
|
+
getCachedSpecs,
|
|
299
|
+
isCacheValid,
|
|
300
|
+
};
|