@friggframework/devtools 2.0.0-next.29 → 2.0.0-next.30

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 (164) hide show
  1. package/frigg-cli/.eslintrc.js +141 -0
  2. package/frigg-cli/__tests__/jest.config.js +102 -0
  3. package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
  4. package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
  5. package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
  6. package/frigg-cli/__tests__/utils/command-tester.js +170 -0
  7. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  8. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  9. package/frigg-cli/__tests__/utils/test-setup.js +286 -0
  10. package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
  11. package/frigg-cli/generate-command/azure-generator.js +43 -0
  12. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  13. package/frigg-cli/generate-command/index.js +332 -0
  14. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  15. package/frigg-cli/index.js +19 -1
  16. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  17. package/frigg-cli/init-command/index.js +93 -0
  18. package/frigg-cli/init-command/template-handler.js +143 -0
  19. package/frigg-cli/package.json +51 -0
  20. package/frigg-cli/test/init-command.test.js +180 -0
  21. package/frigg-cli/test/npm-registry.test.js +319 -0
  22. package/frigg-cli/ui-command/index.js +154 -0
  23. package/frigg-cli/utils/app-resolver.js +319 -0
  24. package/frigg-cli/utils/backend-path.js +25 -0
  25. package/frigg-cli/utils/npm-registry.js +167 -0
  26. package/frigg-cli/utils/process-manager.js +199 -0
  27. package/frigg-cli/utils/repo-detection.js +405 -0
  28. package/infrastructure/serverless-template.js +177 -292
  29. package/management-ui/.eslintrc.js +22 -0
  30. package/management-ui/README.md +203 -0
  31. package/management-ui/components.json +21 -0
  32. package/management-ui/docs/phase2-integration-guide.md +320 -0
  33. package/management-ui/{dist/index.html → index.html} +1 -2
  34. package/management-ui/package-lock.json +16517 -0
  35. package/management-ui/package.json +76 -0
  36. package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
  37. package/management-ui/postcss.config.js +6 -0
  38. package/management-ui/server/api/backend.js +256 -0
  39. package/management-ui/server/api/cli.js +315 -0
  40. package/management-ui/server/api/codegen.js +663 -0
  41. package/management-ui/server/api/connections.js +857 -0
  42. package/management-ui/server/api/discovery.js +185 -0
  43. package/management-ui/server/api/environment/index.js +1 -0
  44. package/management-ui/server/api/environment/router.js +378 -0
  45. package/management-ui/server/api/environment.js +328 -0
  46. package/management-ui/server/api/integrations.js +876 -0
  47. package/management-ui/server/api/logs.js +248 -0
  48. package/management-ui/server/api/monitoring.js +282 -0
  49. package/management-ui/server/api/open-ide.js +31 -0
  50. package/management-ui/server/api/project.js +1029 -0
  51. package/management-ui/server/api/users/sessions.js +371 -0
  52. package/management-ui/server/api/users/simulation.js +254 -0
  53. package/management-ui/server/api/users.js +362 -0
  54. package/management-ui/server/api-contract.md +275 -0
  55. package/management-ui/server/index.js +873 -0
  56. package/management-ui/server/middleware/errorHandler.js +93 -0
  57. package/management-ui/server/middleware/security.js +32 -0
  58. package/management-ui/server/processManager.js +296 -0
  59. package/management-ui/server/server.js +346 -0
  60. package/management-ui/server/services/aws-monitor.js +413 -0
  61. package/management-ui/server/services/npm-registry.js +347 -0
  62. package/management-ui/server/services/template-engine.js +538 -0
  63. package/management-ui/server/utils/cliIntegration.js +220 -0
  64. package/management-ui/server/utils/environment/auditLogger.js +471 -0
  65. package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
  66. package/management-ui/server/utils/environment/encryption.js +278 -0
  67. package/management-ui/server/utils/environment/envFileManager.js +286 -0
  68. package/management-ui/server/utils/import-commonjs.js +28 -0
  69. package/management-ui/server/utils/response.js +83 -0
  70. package/management-ui/server/websocket/handler.js +325 -0
  71. package/management-ui/src/App.jsx +109 -0
  72. package/management-ui/src/components/AppRouter.jsx +65 -0
  73. package/management-ui/src/components/Button.jsx +70 -0
  74. package/management-ui/src/components/Card.jsx +97 -0
  75. package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
  76. package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
  77. package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
  78. package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
  79. package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
  80. package/management-ui/src/components/ErrorBoundary.jsx +73 -0
  81. package/management-ui/src/components/IntegrationCard.jsx +481 -0
  82. package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
  83. package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
  84. package/management-ui/src/components/IntegrationStatus.jsx +336 -0
  85. package/management-ui/src/components/Layout.jsx +716 -0
  86. package/management-ui/src/components/LoadingSpinner.jsx +113 -0
  87. package/management-ui/src/components/RepositoryPicker.jsx +248 -0
  88. package/management-ui/src/components/SessionMonitor.jsx +350 -0
  89. package/management-ui/src/components/StatusBadge.jsx +208 -0
  90. package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
  91. package/management-ui/src/components/UserSimulation.jsx +327 -0
  92. package/management-ui/src/components/Welcome.jsx +434 -0
  93. package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
  94. package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
  95. package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
  96. package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
  97. package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
  98. package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
  99. package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
  100. package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
  101. package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
  102. package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
  103. package/management-ui/src/components/codegen/index.js +10 -0
  104. package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
  105. package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
  106. package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
  107. package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
  108. package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
  109. package/management-ui/src/components/connections/index.js +5 -0
  110. package/management-ui/src/components/index.js +21 -0
  111. package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
  112. package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
  113. package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
  114. package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
  115. package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
  116. package/management-ui/src/components/monitoring/index.js +6 -0
  117. package/management-ui/src/components/monitoring/monitoring.css +218 -0
  118. package/management-ui/src/components/theme-provider.jsx +52 -0
  119. package/management-ui/src/components/theme-toggle.jsx +39 -0
  120. package/management-ui/src/components/ui/badge.tsx +36 -0
  121. package/management-ui/src/components/ui/button.test.jsx +56 -0
  122. package/management-ui/src/components/ui/button.tsx +57 -0
  123. package/management-ui/src/components/ui/card.tsx +76 -0
  124. package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
  125. package/management-ui/src/components/ui/select.tsx +157 -0
  126. package/management-ui/src/components/ui/skeleton.jsx +15 -0
  127. package/management-ui/src/hooks/useFrigg.jsx +601 -0
  128. package/management-ui/src/hooks/useSocket.jsx +58 -0
  129. package/management-ui/src/index.css +193 -0
  130. package/management-ui/src/lib/utils.ts +6 -0
  131. package/management-ui/src/main.jsx +10 -0
  132. package/management-ui/src/pages/CodeGeneration.jsx +14 -0
  133. package/management-ui/src/pages/Connections.jsx +252 -0
  134. package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
  135. package/management-ui/src/pages/Dashboard.jsx +311 -0
  136. package/management-ui/src/pages/Environment.jsx +314 -0
  137. package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
  138. package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
  139. package/management-ui/src/pages/IntegrationTest.jsx +742 -0
  140. package/management-ui/src/pages/Integrations.jsx +253 -0
  141. package/management-ui/src/pages/Monitoring.jsx +17 -0
  142. package/management-ui/src/pages/Simulation.jsx +155 -0
  143. package/management-ui/src/pages/Users.jsx +492 -0
  144. package/management-ui/src/services/api.js +41 -0
  145. package/management-ui/src/services/apiModuleService.js +193 -0
  146. package/management-ui/src/services/websocket-handlers.js +120 -0
  147. package/management-ui/src/test/api/project.test.js +273 -0
  148. package/management-ui/src/test/components/Welcome.test.jsx +378 -0
  149. package/management-ui/src/test/mocks/server.js +178 -0
  150. package/management-ui/src/test/setup.js +61 -0
  151. package/management-ui/src/test/utils/test-utils.jsx +134 -0
  152. package/management-ui/src/utils/repository.js +98 -0
  153. package/management-ui/src/utils/repository.test.js +118 -0
  154. package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
  155. package/management-ui/tailwind.config.js +63 -0
  156. package/management-ui/tsconfig.json +37 -0
  157. package/management-ui/tsconfig.node.json +10 -0
  158. package/management-ui/vite.config.js +26 -0
  159. package/management-ui/vitest.config.js +38 -0
  160. package/package.json +5 -5
  161. package/management-ui/dist/assets/index-BA21WgFa.js +0 -1221
  162. package/management-ui/dist/assets/index-CbM64Oba.js +0 -1221
  163. package/management-ui/dist/assets/index-CkvseXTC.css +0 -1
  164. /package/management-ui/{dist/assets/FriggLogo-B7Xx8ZW1.svg → src/assets/FriggLogo.svg} +0 -0
@@ -0,0 +1,319 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ class AppResolver {
6
+ constructor() {
7
+ this.cache = new Map();
8
+ }
9
+
10
+ async resolveAppPath(options = {}) {
11
+ const cacheKey = JSON.stringify(options);
12
+ if (this.cache.has(cacheKey)) {
13
+ return this.cache.get(cacheKey);
14
+ }
15
+
16
+ let resolvedPath;
17
+
18
+ // Priority 1: Explicit flags (--app-path, --config, --app)
19
+ if (options.appPath || options.config || options.app) {
20
+ const explicitPath = options.appPath || options.config || options.app;
21
+ resolvedPath = await this.validateAndResolvePath(explicitPath);
22
+ if (resolvedPath) {
23
+ this.cache.set(cacheKey, resolvedPath);
24
+ return resolvedPath;
25
+ }
26
+ throw new Error(`Invalid app path specified: ${explicitPath}`);
27
+ }
28
+
29
+ // Priority 2: Environment variable
30
+ if (process.env.FRIGG_APP_PATH) {
31
+ resolvedPath = await this.validateAndResolvePath(process.env.FRIGG_APP_PATH);
32
+ if (resolvedPath) {
33
+ this.cache.set(cacheKey, resolvedPath);
34
+ return resolvedPath;
35
+ }
36
+ console.warn(`Warning: FRIGG_APP_PATH environment variable points to invalid path: ${process.env.FRIGG_APP_PATH}`);
37
+ }
38
+
39
+ // Priority 3: Current directory auto-detection (backward compatibility)
40
+ resolvedPath = await this.autoDetectFriggApp();
41
+ if (resolvedPath) {
42
+ this.cache.set(cacheKey, resolvedPath);
43
+ return resolvedPath;
44
+ }
45
+
46
+ // Priority 4: Search common development directories
47
+ resolvedPath = await this.searchCommonDirectories();
48
+ if (resolvedPath) {
49
+ this.cache.set(cacheKey, resolvedPath);
50
+ return resolvedPath;
51
+ }
52
+
53
+ throw new Error('No Frigg application found. Use --app-path to specify the application directory.');
54
+ }
55
+
56
+ async validateAndResolvePath(inputPath) {
57
+ if (!inputPath) return null;
58
+
59
+ // Handle different path formats
60
+ let resolvedPath;
61
+ if (inputPath.startsWith('~/')) {
62
+ resolvedPath = path.join(os.homedir(), inputPath.slice(2));
63
+ } else if (path.isAbsolute(inputPath)) {
64
+ resolvedPath = inputPath;
65
+ } else {
66
+ resolvedPath = path.resolve(process.cwd(), inputPath);
67
+ }
68
+
69
+ try {
70
+ const stats = await fs.stat(resolvedPath);
71
+ if (!stats.isDirectory()) {
72
+ // If it's a file, check if it's a config file and use its directory
73
+ if (await this.isConfigFile(resolvedPath)) {
74
+ resolvedPath = path.dirname(resolvedPath);
75
+ } else {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ // Validate that this is a Frigg application
81
+ if (await this.isFriggApplication(resolvedPath)) {
82
+ return resolvedPath;
83
+ }
84
+ } catch (error) {
85
+ // Path doesn't exist or is not accessible
86
+ return null;
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ async isConfigFile(filePath) {
93
+ const basename = path.basename(filePath);
94
+ const configFiles = [
95
+ 'frigg.config.js',
96
+ 'frigg.config.json',
97
+ '.friggrc',
98
+ '.friggrc.js',
99
+ '.friggrc.json',
100
+ 'package.json'
101
+ ];
102
+
103
+ return configFiles.includes(basename);
104
+ }
105
+
106
+ async isFriggApplication(dirPath) {
107
+ try {
108
+ // Check for package.json with Frigg dependencies
109
+ const packageJsonPath = path.join(dirPath, 'package.json');
110
+ try {
111
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
112
+ const packageJson = JSON.parse(packageJsonContent);
113
+
114
+ // Check for @friggframework dependencies
115
+ const deps = {
116
+ ...packageJson.dependencies,
117
+ ...packageJson.devDependencies,
118
+ ...packageJson.peerDependencies
119
+ };
120
+
121
+ if (Object.keys(deps).some(dep => dep.startsWith('@friggframework/'))) {
122
+ return true;
123
+ }
124
+
125
+ // Check for frigg scripts
126
+ if (packageJson.scripts) {
127
+ const scriptNames = Object.keys(packageJson.scripts);
128
+ if (scriptNames.some(script => script.includes('frigg'))) {
129
+ return true;
130
+ }
131
+ }
132
+ } catch (error) {
133
+ // package.json doesn't exist or is invalid, continue checking other indicators
134
+ }
135
+
136
+ // Check for Frigg configuration files
137
+ const configFiles = [
138
+ 'frigg.config.js',
139
+ 'frigg.config.json',
140
+ '.friggrc',
141
+ '.friggrc.js',
142
+ '.friggrc.json'
143
+ ];
144
+
145
+ for (const configFile of configFiles) {
146
+ try {
147
+ await fs.access(path.join(dirPath, configFile));
148
+ return true;
149
+ } catch (error) {
150
+ // File doesn't exist, continue
151
+ }
152
+ }
153
+
154
+ // Check for Frigg-specific directories
155
+ const friggDirectories = [
156
+ '.frigg',
157
+ 'frigg-modules',
158
+ 'api-modules'
159
+ ];
160
+
161
+ for (const friggDir of friggDirectories) {
162
+ try {
163
+ const dirStat = await fs.stat(path.join(dirPath, friggDir));
164
+ if (dirStat.isDirectory()) {
165
+ return true;
166
+ }
167
+ } catch (error) {
168
+ // Directory doesn't exist, continue
169
+ }
170
+ }
171
+
172
+ // Check for serverless.yml with Frigg references
173
+ try {
174
+ const serverlessPath = path.join(dirPath, 'serverless.yml');
175
+ const serverlessContent = await fs.readFile(serverlessPath, 'utf8');
176
+ if (serverlessContent.includes('frigg') || serverlessContent.includes('Frigg')) {
177
+ return true;
178
+ }
179
+ } catch (error) {
180
+ // serverless.yml doesn't exist or can't be read
181
+ }
182
+
183
+ // Check for infrastructure.js (common in Frigg apps)
184
+ try {
185
+ await fs.access(path.join(dirPath, 'infrastructure.js'));
186
+ return true;
187
+ } catch (error) {
188
+ // infrastructure.js doesn't exist
189
+ }
190
+
191
+ return false;
192
+ } catch (error) {
193
+ return false;
194
+ }
195
+ }
196
+
197
+ async autoDetectFriggApp() {
198
+ // Start from current directory and search up to 3 levels
199
+ let currentDir = process.cwd();
200
+
201
+ for (let i = 0; i < 3; i++) {
202
+ if (await this.isFriggApplication(currentDir)) {
203
+ return currentDir;
204
+ }
205
+
206
+ const parentDir = path.dirname(currentDir);
207
+ if (parentDir === currentDir) {
208
+ // Reached filesystem root
209
+ break;
210
+ }
211
+ currentDir = parentDir;
212
+ }
213
+
214
+ return null;
215
+ }
216
+
217
+ async searchCommonDirectories() {
218
+ const commonDirs = [
219
+ path.join(os.homedir(), 'Documents'),
220
+ path.join(os.homedir(), 'Projects'),
221
+ path.join(os.homedir(), 'Development'),
222
+ path.join(os.homedir(), 'dev'),
223
+ path.join(os.homedir(), 'workspace')
224
+ ];
225
+
226
+ for (const baseDir of commonDirs) {
227
+ try {
228
+ const friggApp = await this.searchDirectoryRecursively(baseDir, 3);
229
+ if (friggApp) {
230
+ return friggApp;
231
+ }
232
+ } catch (error) {
233
+ // Directory doesn't exist or can't be accessed, continue
234
+ }
235
+ }
236
+
237
+ return null;
238
+ }
239
+
240
+ async searchDirectoryRecursively(dirPath, maxDepth) {
241
+ if (maxDepth <= 0) return null;
242
+
243
+ try {
244
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
245
+
246
+ // First check if current directory is a Frigg app
247
+ if (await this.isFriggApplication(dirPath)) {
248
+ return dirPath;
249
+ }
250
+
251
+ // Then search subdirectories
252
+ for (const entry of entries) {
253
+ if (!entry.isDirectory()) continue;
254
+
255
+ // Skip common directories that shouldn't contain Frigg apps
256
+ const skipDirs = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];
257
+ if (skipDirs.includes(entry.name)) continue;
258
+
259
+ const subDirPath = path.join(dirPath, entry.name);
260
+ const result = await this.searchDirectoryRecursively(subDirPath, maxDepth - 1);
261
+ if (result) {
262
+ return result;
263
+ }
264
+ }
265
+ } catch (error) {
266
+ // Directory can't be read, skip
267
+ }
268
+
269
+ return null;
270
+ }
271
+
272
+ async loadAppConfig(appPath) {
273
+ const configPaths = [
274
+ path.join(appPath, 'frigg.config.js'),
275
+ path.join(appPath, 'frigg.config.json'),
276
+ path.join(appPath, '.friggrc.js'),
277
+ path.join(appPath, '.friggrc.json'),
278
+ path.join(appPath, '.friggrc')
279
+ ];
280
+
281
+ for (const configPath of configPaths) {
282
+ try {
283
+ const stats = await fs.stat(configPath);
284
+ if (stats.isFile()) {
285
+ if (configPath.endsWith('.js')) {
286
+ delete require.cache[require.resolve(configPath)];
287
+ return require(configPath);
288
+ } else {
289
+ const content = await fs.readFile(configPath, 'utf8');
290
+ return JSON.parse(content);
291
+ }
292
+ }
293
+ } catch (error) {
294
+ // Config file doesn't exist or can't be read, continue
295
+ }
296
+ }
297
+
298
+ // Fallback to package.json frigg configuration
299
+ try {
300
+ const packageJsonPath = path.join(appPath, 'package.json');
301
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
302
+ const packageJson = JSON.parse(packageJsonContent);
303
+
304
+ if (packageJson.frigg) {
305
+ return packageJson.frigg;
306
+ }
307
+ } catch (error) {
308
+ // package.json doesn't exist or doesn't have frigg config
309
+ }
310
+
311
+ return {};
312
+ }
313
+
314
+ clearCache() {
315
+ this.cache.clear();
316
+ }
317
+ }
318
+
319
+ module.exports = { AppResolver };
@@ -0,0 +1,25 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ /**
5
+ * Finds the nearest backend package.json by traversing up the directory tree
6
+ * @param {string} startDir - Directory to start searching from
7
+ * @returns {string|null} - Path to the backend directory or null if not found
8
+ */
9
+ function findNearestBackendPackageJson(startDir = process.cwd()) {
10
+ let currentDir = startDir;
11
+
12
+ while (currentDir !== path.dirname(currentDir)) {
13
+ const backendPath = path.join(currentDir, 'backend', 'package.json');
14
+ if (fs.existsSync(backendPath)) {
15
+ return path.dirname(backendPath);
16
+ }
17
+ currentDir = path.dirname(currentDir);
18
+ }
19
+
20
+ return null;
21
+ }
22
+
23
+ module.exports = {
24
+ findNearestBackendPackageJson
25
+ };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * NPM Registry Service for CLI
3
+ * CommonJS version of the npm-registry service
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const axios = require('axios');
9
+ const NodeCache = require('node-cache');
10
+ const semver = require('semver');
11
+
12
+ class NPMRegistryService {
13
+ constructor() {
14
+ // Cache with 1 hour TTL by default
15
+ this.cache = new NodeCache({
16
+ stdTTL: 3600,
17
+ checkperiod: 600,
18
+ useClones: false
19
+ });
20
+
21
+ this.npmRegistryUrl = 'https://registry.npmjs.org';
22
+ this.searchUrl = `${this.npmRegistryUrl}/-/v1/search`;
23
+ this.packageScope = '@friggframework';
24
+ this.modulePrefix = 'api-module-';
25
+ }
26
+
27
+ /**
28
+ * Search for all @friggframework/api-module-* packages
29
+ * @param {Object} options - Search options
30
+ * @param {boolean} options.includePrerelease - Include prerelease versions
31
+ * @param {boolean} options.forceRefresh - Force cache refresh
32
+ * @returns {Promise<Array>} Array of package information
33
+ */
34
+ async searchApiModules(options = {}) {
35
+ const cacheKey = `api-modules-${JSON.stringify(options)}`;
36
+
37
+ // Check cache first unless force refresh is requested
38
+ if (!options.forceRefresh) {
39
+ const cached = this.cache.get(cacheKey);
40
+ if (cached) {
41
+ return cached;
42
+ }
43
+ }
44
+
45
+ try {
46
+ // Search for packages matching our pattern
47
+ const searchQuery = `${this.packageScope}/${this.modulePrefix}`;
48
+ const response = await axios.get(this.searchUrl, {
49
+ params: {
50
+ text: searchQuery,
51
+ size: 250, // Get up to 250 results
52
+ quality: 0.65,
53
+ popularity: 0.98,
54
+ maintenance: 0.5
55
+ },
56
+ timeout: 10000
57
+ });
58
+
59
+ const packages = response.data.objects
60
+ .filter(obj => obj.package.name.startsWith(`${this.packageScope}/${this.modulePrefix}`))
61
+ .map(obj => this.formatPackageInfo(obj.package));
62
+
63
+ // Filter out prereleases if requested
64
+ const filtered = options.includePrerelease
65
+ ? packages
66
+ : packages.filter(pkg => !semver.prerelease(pkg.version));
67
+
68
+ // Cache the results
69
+ this.cache.set(cacheKey, filtered);
70
+
71
+ return filtered;
72
+ } catch (error) {
73
+ console.error('Error searching NPM registry:', error.message);
74
+ // Return empty array on error to allow offline usage
75
+ return [];
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Extract integration name from package name
81
+ * @private
82
+ */
83
+ extractIntegrationName(packageName) {
84
+ return packageName
85
+ .replace(`${this.packageScope}/${this.modulePrefix}`, '')
86
+ .replace(/-/g, ' ')
87
+ .replace(/\b\w/g, l => l.toUpperCase());
88
+ }
89
+
90
+ /**
91
+ * Format package information for API response
92
+ * @private
93
+ */
94
+ formatPackageInfo(pkg) {
95
+ return {
96
+ name: pkg.name,
97
+ version: pkg.version,
98
+ description: pkg.description,
99
+ keywords: pkg.keywords || [],
100
+ author: pkg.author,
101
+ publisher: pkg.publisher,
102
+ date: pkg.date,
103
+ links: pkg.links,
104
+ integrationName: this.extractIntegrationName(pkg.name),
105
+ category: this.categorizeModule(pkg)
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Categorize module based on keywords and name
111
+ * @private
112
+ */
113
+ categorizeModule(module) {
114
+ const name = module.name?.toLowerCase() || '';
115
+ const keywords = module.keywords?.map(k => k.toLowerCase()) || [];
116
+ const allTerms = [...keywords, name];
117
+
118
+ // Categories based on common integration types - ordered by specificity
119
+ const categories = {
120
+ 'Marketing': ['marketing', 'mailchimp', 'campaign', 'automation', 'klaviyo', 'activecampaign'],
121
+ 'CRM': ['crm', 'customer', 'salesforce', 'hubspot', 'pipedrive', 'zoho'],
122
+ 'E-commerce': ['ecommerce', 'shop', 'store', 'payment', 'stripe', 'paypal', 'shopify', 'woocommerce'],
123
+ 'Analytics': ['analytics', 'tracking', 'google-analytics', 'mixpanel', 'segment', 'amplitude'],
124
+ 'Social Media': ['social', 'facebook', 'twitter', 'instagram', 'linkedin', 'youtube'],
125
+ 'Project Management': ['project', 'task', 'jira', 'trello', 'asana', 'monday', 'notion'],
126
+ 'Storage': ['storage', 'file', 'dropbox', 'google-drive', 's3', 'box', 'onedrive'],
127
+ 'Productivity': ['spreadsheet', 'google-sheets', 'airtable', 'calendar', 'todo'],
128
+ 'Development': ['github', 'gitlab', 'bitbucket', 'git', 'ci', 'cd', 'jenkins'],
129
+ 'Support': ['support', 'zendesk', 'freshdesk', 'intercom', 'helpdesk'],
130
+ 'Finance': ['accounting', 'quickbooks', 'xero', 'sage', 'invoice', 'billing'],
131
+ 'Communication': ['email', 'sms', 'chat', 'messaging', 'slack', 'discord', 'twilio', 'sendgrid'],
132
+ 'Other': []
133
+ };
134
+
135
+ for (const [category, terms] of Object.entries(categories)) {
136
+ if (category === 'Other') continue;
137
+
138
+ if (terms.some(term => allTerms.some(t => t.includes(term)))) {
139
+ return category;
140
+ }
141
+ }
142
+
143
+ return 'Other';
144
+ }
145
+
146
+ /**
147
+ * Get grouped modules by integration type
148
+ * @returns {Promise<Object>} Modules grouped by type
149
+ */
150
+ async getModulesByType() {
151
+ const modules = await this.searchApiModules();
152
+
153
+ const grouped = modules.reduce((acc, module) => {
154
+ const type = module.category;
155
+ if (!acc[type]) {
156
+ acc[type] = [];
157
+ }
158
+ acc[type].push(module);
159
+ return acc;
160
+ }, {});
161
+
162
+ return grouped;
163
+ }
164
+ }
165
+
166
+ // Export singleton instance
167
+ module.exports = new NPMRegistryService();