@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.
- package/frigg-cli/.eslintrc.js +141 -0
- package/frigg-cli/__tests__/jest.config.js +102 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
- package/frigg-cli/__tests__/utils/command-tester.js +170 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +286 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/index.js +19 -1
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/package.json +51 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +25 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/serverless-template.js +177 -292
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/docs/phase2-integration-guide.md +320 -0
- package/management-ui/{dist/index.html → index.html} +1 -2
- package/management-ui/package-lock.json +16517 -0
- package/management-ui/package.json +76 -0
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
- package/management-ui/postcss.config.js +6 -0
- package/management-ui/server/api/backend.js +256 -0
- package/management-ui/server/api/cli.js +315 -0
- package/management-ui/server/api/codegen.js +663 -0
- package/management-ui/server/api/connections.js +857 -0
- package/management-ui/server/api/discovery.js +185 -0
- package/management-ui/server/api/environment/index.js +1 -0
- package/management-ui/server/api/environment/router.js +378 -0
- package/management-ui/server/api/environment.js +328 -0
- package/management-ui/server/api/integrations.js +876 -0
- package/management-ui/server/api/logs.js +248 -0
- package/management-ui/server/api/monitoring.js +282 -0
- package/management-ui/server/api/open-ide.js +31 -0
- package/management-ui/server/api/project.js +1029 -0
- package/management-ui/server/api/users/sessions.js +371 -0
- package/management-ui/server/api/users/simulation.js +254 -0
- package/management-ui/server/api/users.js +362 -0
- package/management-ui/server/api-contract.md +275 -0
- package/management-ui/server/index.js +873 -0
- package/management-ui/server/middleware/errorHandler.js +93 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +346 -0
- package/management-ui/server/services/aws-monitor.js +413 -0
- package/management-ui/server/services/npm-registry.js +347 -0
- package/management-ui/server/services/template-engine.js +538 -0
- package/management-ui/server/utils/cliIntegration.js +220 -0
- package/management-ui/server/utils/environment/auditLogger.js +471 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
- package/management-ui/server/utils/environment/encryption.js +278 -0
- package/management-ui/server/utils/environment/envFileManager.js +286 -0
- package/management-ui/server/utils/import-commonjs.js +28 -0
- package/management-ui/server/utils/response.js +83 -0
- package/management-ui/server/websocket/handler.js +325 -0
- package/management-ui/src/App.jsx +109 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +70 -0
- package/management-ui/src/components/Card.jsx +97 -0
- package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
- package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
- package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
- package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
- package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
- package/management-ui/src/components/ErrorBoundary.jsx +73 -0
- package/management-ui/src/components/IntegrationCard.jsx +481 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +336 -0
- package/management-ui/src/components/Layout.jsx +716 -0
- package/management-ui/src/components/LoadingSpinner.jsx +113 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +350 -0
- package/management-ui/src/components/StatusBadge.jsx +208 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
- package/management-ui/src/components/UserSimulation.jsx +327 -0
- package/management-ui/src/components/Welcome.jsx +434 -0
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
- package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
- package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
- package/management-ui/src/components/codegen/index.js +10 -0
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
- package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
- package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
- package/management-ui/src/components/connections/index.js +5 -0
- package/management-ui/src/components/index.js +21 -0
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
- package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
- package/management-ui/src/components/monitoring/index.js +6 -0
- package/management-ui/src/components/monitoring/monitoring.css +218 -0
- package/management-ui/src/components/theme-provider.jsx +52 -0
- package/management-ui/src/components/theme-toggle.jsx +39 -0
- package/management-ui/src/components/ui/badge.tsx +36 -0
- package/management-ui/src/components/ui/button.test.jsx +56 -0
- package/management-ui/src/components/ui/button.tsx +57 -0
- package/management-ui/src/components/ui/card.tsx +76 -0
- package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
- package/management-ui/src/components/ui/select.tsx +157 -0
- package/management-ui/src/components/ui/skeleton.jsx +15 -0
- package/management-ui/src/hooks/useFrigg.jsx +601 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +193 -0
- package/management-ui/src/lib/utils.ts +6 -0
- package/management-ui/src/main.jsx +10 -0
- package/management-ui/src/pages/CodeGeneration.jsx +14 -0
- package/management-ui/src/pages/Connections.jsx +252 -0
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
- package/management-ui/src/pages/Dashboard.jsx +311 -0
- package/management-ui/src/pages/Environment.jsx +314 -0
- package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
- package/management-ui/src/pages/IntegrationTest.jsx +742 -0
- package/management-ui/src/pages/Integrations.jsx +253 -0
- package/management-ui/src/pages/Monitoring.jsx +17 -0
- package/management-ui/src/pages/Simulation.jsx +155 -0
- package/management-ui/src/pages/Users.jsx +492 -0
- package/management-ui/src/services/api.js +41 -0
- package/management-ui/src/services/apiModuleService.js +193 -0
- package/management-ui/src/services/websocket-handlers.js +120 -0
- package/management-ui/src/test/api/project.test.js +273 -0
- package/management-ui/src/test/components/Welcome.test.jsx +378 -0
- package/management-ui/src/test/mocks/server.js +178 -0
- package/management-ui/src/test/setup.js +61 -0
- package/management-ui/src/test/utils/test-utils.jsx +134 -0
- package/management-ui/src/utils/repository.js +98 -0
- package/management-ui/src/utils/repository.test.js +118 -0
- package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
- package/management-ui/tailwind.config.js +63 -0
- package/management-ui/tsconfig.json +37 -0
- package/management-ui/tsconfig.node.json +10 -0
- package/management-ui/vite.config.js +26 -0
- package/management-ui/vitest.config.js +38 -0
- package/package.json +5 -5
- package/management-ui/dist/assets/index-BA21WgFa.js +0 -1221
- package/management-ui/dist/assets/index-CbM64Oba.js +0 -1221
- package/management-ui/dist/assets/index-CkvseXTC.css +0 -1
- /package/management-ui/{dist/assets/FriggLogo-B7Xx8ZW1.svg → src/assets/FriggLogo.svg} +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Service for discovering and fetching Frigg API modules from npm
|
|
2
|
+
export class APIModuleService {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.npmRegistry = 'https://registry.npmjs.org';
|
|
5
|
+
this.scope = '@friggframework';
|
|
6
|
+
this.modulePrefix = 'api-module-';
|
|
7
|
+
|
|
8
|
+
// Cache for module data
|
|
9
|
+
this.moduleCache = new Map();
|
|
10
|
+
this.cacheExpiry = 60 * 60 * 1000; // 1 hour
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Fetch all available API modules from npm
|
|
14
|
+
async getAllModules() {
|
|
15
|
+
const cacheKey = 'all-modules';
|
|
16
|
+
const cached = this.getFromCache(cacheKey);
|
|
17
|
+
if (cached) return cached;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Search for all @friggframework/api-module-* packages
|
|
21
|
+
const searchUrl = `${this.npmRegistry}/-/v1/search?text=${encodeURIComponent(this.scope + '/' + this.modulePrefix)}&size=250`;
|
|
22
|
+
const response = await fetch(searchUrl);
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`Failed to fetch modules: ${response.status}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
const modules = data.objects
|
|
30
|
+
.filter(pkg => pkg.package.name.startsWith(`${this.scope}/${this.modulePrefix}`))
|
|
31
|
+
.map(pkg => ({
|
|
32
|
+
name: pkg.package.name,
|
|
33
|
+
displayName: this.formatDisplayName(pkg.package.name),
|
|
34
|
+
version: pkg.package.version,
|
|
35
|
+
description: pkg.package.description,
|
|
36
|
+
keywords: pkg.package.keywords || [],
|
|
37
|
+
links: pkg.package.links || {},
|
|
38
|
+
date: pkg.package.date,
|
|
39
|
+
publisher: pkg.package.publisher,
|
|
40
|
+
maintainers: pkg.package.maintainers || []
|
|
41
|
+
}))
|
|
42
|
+
.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
43
|
+
|
|
44
|
+
this.setCache(cacheKey, modules);
|
|
45
|
+
return modules;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error fetching API modules:', error);
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get detailed information about a specific module
|
|
53
|
+
async getModuleDetails(moduleName) {
|
|
54
|
+
const cacheKey = `module-${moduleName}`;
|
|
55
|
+
const cached = this.getFromCache(cacheKey);
|
|
56
|
+
if (cached) return cached;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const packageUrl = `${this.npmRegistry}/${moduleName}`;
|
|
60
|
+
const response = await fetch(packageUrl);
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Failed to fetch module details: ${response.status}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
const latestVersion = data['dist-tags'].latest;
|
|
68
|
+
const versionData = data.versions[latestVersion];
|
|
69
|
+
|
|
70
|
+
const details = {
|
|
71
|
+
name: data.name,
|
|
72
|
+
displayName: this.formatDisplayName(data.name),
|
|
73
|
+
version: latestVersion,
|
|
74
|
+
description: data.description,
|
|
75
|
+
readme: data.readme,
|
|
76
|
+
homepage: data.homepage,
|
|
77
|
+
repository: data.repository,
|
|
78
|
+
keywords: versionData.keywords || [],
|
|
79
|
+
dependencies: versionData.dependencies || {},
|
|
80
|
+
peerDependencies: versionData.peerDependencies || {},
|
|
81
|
+
maintainers: data.maintainers || [],
|
|
82
|
+
time: data.time,
|
|
83
|
+
// Extract configuration from package.json if available
|
|
84
|
+
friggConfig: versionData.frigg || {},
|
|
85
|
+
authType: this.detectAuthType(versionData),
|
|
86
|
+
requiredFields: this.extractRequiredFields(versionData)
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.setCache(cacheKey, details);
|
|
90
|
+
return details;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(`Error fetching details for ${moduleName}:`, error);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Format module name for display
|
|
98
|
+
formatDisplayName(packageName) {
|
|
99
|
+
const moduleName = packageName
|
|
100
|
+
.replace(`${this.scope}/`, '')
|
|
101
|
+
.replace(this.modulePrefix, '')
|
|
102
|
+
.split('-')
|
|
103
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
104
|
+
.join(' ');
|
|
105
|
+
return moduleName;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Detect authentication type from module
|
|
109
|
+
detectAuthType(packageData) {
|
|
110
|
+
const deps = { ...packageData.dependencies, ...packageData.peerDependencies };
|
|
111
|
+
const keywords = packageData.keywords || [];
|
|
112
|
+
const description = (packageData.description || '').toLowerCase();
|
|
113
|
+
|
|
114
|
+
if (deps['@friggframework/oauth2'] || keywords.includes('oauth2')) {
|
|
115
|
+
return 'oauth2';
|
|
116
|
+
}
|
|
117
|
+
if (deps['@friggframework/oauth1'] || keywords.includes('oauth1')) {
|
|
118
|
+
return 'oauth1';
|
|
119
|
+
}
|
|
120
|
+
if (keywords.includes('api-key') || description.includes('api key')) {
|
|
121
|
+
return 'api-key';
|
|
122
|
+
}
|
|
123
|
+
if (keywords.includes('basic-auth') || description.includes('basic auth')) {
|
|
124
|
+
return 'basic-auth';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return 'custom';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Extract required fields from module configuration
|
|
131
|
+
extractRequiredFields(packageData) {
|
|
132
|
+
const friggConfig = packageData.frigg || {};
|
|
133
|
+
const fields = [];
|
|
134
|
+
|
|
135
|
+
// Check for defined required fields in frigg config
|
|
136
|
+
if (friggConfig.requiredFields) {
|
|
137
|
+
return friggConfig.requiredFields;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Otherwise, try to infer from common patterns
|
|
141
|
+
const authType = this.detectAuthType(packageData);
|
|
142
|
+
|
|
143
|
+
switch (authType) {
|
|
144
|
+
case 'oauth2':
|
|
145
|
+
fields.push(
|
|
146
|
+
{ name: 'client_id', label: 'Client ID', type: 'string', required: true },
|
|
147
|
+
{ name: 'client_secret', label: 'Client Secret', type: 'password', required: true },
|
|
148
|
+
{ name: 'redirect_uri', label: 'Redirect URI', type: 'string', required: true }
|
|
149
|
+
);
|
|
150
|
+
break;
|
|
151
|
+
case 'api-key':
|
|
152
|
+
fields.push(
|
|
153
|
+
{ name: 'api_key', label: 'API Key', type: 'password', required: true }
|
|
154
|
+
);
|
|
155
|
+
break;
|
|
156
|
+
case 'basic-auth':
|
|
157
|
+
fields.push(
|
|
158
|
+
{ name: 'username', label: 'Username', type: 'string', required: true },
|
|
159
|
+
{ name: 'password', label: 'Password', type: 'password', required: true }
|
|
160
|
+
);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return fields;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Cache management
|
|
168
|
+
getFromCache(key) {
|
|
169
|
+
const cached = this.moduleCache.get(key);
|
|
170
|
+
if (!cached) return null;
|
|
171
|
+
|
|
172
|
+
if (Date.now() - cached.timestamp > this.cacheExpiry) {
|
|
173
|
+
this.moduleCache.delete(key);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return cached.data;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
setCache(key, data) {
|
|
181
|
+
this.moduleCache.set(key, {
|
|
182
|
+
data,
|
|
183
|
+
timestamp: Date.now()
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
clearCache() {
|
|
188
|
+
this.moduleCache.clear();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Export singleton instance
|
|
193
|
+
export default new APIModuleService();
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// WebSocket event handlers for integration discovery and installation
|
|
2
|
+
|
|
3
|
+
export const setupIntegrationHandlers = (socket) => {
|
|
4
|
+
// Installation progress handler
|
|
5
|
+
socket.on('integration:install:start', (data) => {
|
|
6
|
+
console.log('Installation started:', data)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
socket.on('integration:install:progress', (data) => {
|
|
10
|
+
console.log('Installation progress:', data)
|
|
11
|
+
// data: { packageName, progress: 0-100, message, status }
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
socket.on('integration:install:complete', (data) => {
|
|
15
|
+
console.log('Installation complete:', data)
|
|
16
|
+
// data: { packageName, version, success: true }
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
socket.on('integration:install:error', (data) => {
|
|
20
|
+
console.error('Installation error:', data)
|
|
21
|
+
// data: { packageName, error, details }
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Uninstallation handlers
|
|
25
|
+
socket.on('integration:uninstall:start', (data) => {
|
|
26
|
+
console.log('Uninstallation started:', data)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
socket.on('integration:uninstall:complete', (data) => {
|
|
30
|
+
console.log('Uninstallation complete:', data)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
socket.on('integration:uninstall:error', (data) => {
|
|
34
|
+
console.error('Uninstallation error:', data)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Update handlers
|
|
38
|
+
socket.on('integration:update:start', (data) => {
|
|
39
|
+
console.log('Update started:', data)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
socket.on('integration:update:progress', (data) => {
|
|
43
|
+
console.log('Update progress:', data)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
socket.on('integration:update:complete', (data) => {
|
|
47
|
+
console.log('Update complete:', data)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
socket.on('integration:update:error', (data) => {
|
|
51
|
+
console.error('Update error:', data)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Health check handlers
|
|
55
|
+
socket.on('integration:health:update', (data) => {
|
|
56
|
+
console.log('Integration health update:', data)
|
|
57
|
+
// data: { packageName, status, health }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Configuration change handlers
|
|
61
|
+
socket.on('integration:config:updated', (data) => {
|
|
62
|
+
console.log('Integration configuration updated:', data)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Test execution handlers
|
|
66
|
+
socket.on('integration:test:start', (data) => {
|
|
67
|
+
console.log('Test started:', data)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
socket.on('integration:test:complete', (data) => {
|
|
71
|
+
console.log('Test complete:', data)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
socket.on('integration:test:error', (data) => {
|
|
75
|
+
console.error('Test error:', data)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return socket
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Emit installation request with progress tracking
|
|
82
|
+
export const installIntegrationWithProgress = (socket, packageName, options = {}) => {
|
|
83
|
+
socket.emit('integration:install', {
|
|
84
|
+
packageName,
|
|
85
|
+
options,
|
|
86
|
+
trackProgress: true
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Emit uninstallation request
|
|
91
|
+
export const uninstallIntegrationWithProgress = (socket, packageName) => {
|
|
92
|
+
socket.emit('integration:uninstall', {
|
|
93
|
+
packageName,
|
|
94
|
+
trackProgress: true
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Emit update request with progress tracking
|
|
99
|
+
export const updateIntegrationWithProgress = (socket, packageName) => {
|
|
100
|
+
socket.emit('integration:update', {
|
|
101
|
+
packageName,
|
|
102
|
+
trackProgress: true
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Request integration health check
|
|
107
|
+
export const checkIntegrationHealth = (socket, packageName) => {
|
|
108
|
+
socket.emit('integration:health:check', {
|
|
109
|
+
packageName
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Test integration endpoint
|
|
114
|
+
export const testIntegrationEndpoint = (socket, integrationName, endpoint, params) => {
|
|
115
|
+
socket.emit('integration:test:endpoint', {
|
|
116
|
+
integrationName,
|
|
117
|
+
endpoint,
|
|
118
|
+
params
|
|
119
|
+
})
|
|
120
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import request from 'supertest'
|
|
3
|
+
import express from 'express'
|
|
4
|
+
|
|
5
|
+
// Mock child_process
|
|
6
|
+
vi.mock('child_process', () => ({
|
|
7
|
+
spawn: vi.fn(),
|
|
8
|
+
exec: vi.fn()
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
// Mock fs/promises
|
|
12
|
+
vi.mock('fs/promises', () => ({
|
|
13
|
+
readFile: vi.fn(),
|
|
14
|
+
stat: vi.fn(),
|
|
15
|
+
access: vi.fn()
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
// Mock the utilities
|
|
19
|
+
vi.mock('../../server/utils/response.js', () => ({
|
|
20
|
+
createStandardResponse: (data) => ({ success: true, data }),
|
|
21
|
+
createErrorResponse: (code, message) => ({ success: false, error: { code, message } }),
|
|
22
|
+
ERROR_CODES: {
|
|
23
|
+
PROJECT_ALREADY_RUNNING: 'PROJECT_ALREADY_RUNNING',
|
|
24
|
+
PROJECT_NOT_RUNNING: 'PROJECT_NOT_RUNNING',
|
|
25
|
+
PROJECT_START_FAILED: 'PROJECT_START_FAILED',
|
|
26
|
+
PROJECT_STOP_FAILED: 'PROJECT_STOP_FAILED',
|
|
27
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
28
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR'
|
|
29
|
+
},
|
|
30
|
+
asyncHandler: (fn) => fn
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
describe('Project API - Repository Discovery', () => {
|
|
34
|
+
let app
|
|
35
|
+
let projectRouter
|
|
36
|
+
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
vi.clearAllMocks()
|
|
39
|
+
|
|
40
|
+
// Create fresh Express app for each test
|
|
41
|
+
app = express()
|
|
42
|
+
app.use(express.json())
|
|
43
|
+
|
|
44
|
+
// Import the router after mocks are set up
|
|
45
|
+
const module = await import('../../server/api/project.js')
|
|
46
|
+
projectRouter = module.default
|
|
47
|
+
app.use('/api/project', projectRouter)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
vi.restoreAllMocks()
|
|
52
|
+
// Clean up environment variables
|
|
53
|
+
delete process.env.AVAILABLE_REPOSITORIES
|
|
54
|
+
delete process.env.REPOSITORY_INFO
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('GET /api/project/repositories', () => {
|
|
58
|
+
it('should return repositories from environment variable when available', async () => {
|
|
59
|
+
const mockRepos = [
|
|
60
|
+
{
|
|
61
|
+
name: 'test-app-1',
|
|
62
|
+
path: '/path/to/app1',
|
|
63
|
+
framework: 'React',
|
|
64
|
+
version: '1.0.0'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'test-app-2',
|
|
68
|
+
path: '/path/to/app2',
|
|
69
|
+
framework: 'Vue',
|
|
70
|
+
version: '2.0.0'
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
const mockCurrentRepo = {
|
|
75
|
+
name: 'Multiple Repositories Available',
|
|
76
|
+
isMultiRepo: true,
|
|
77
|
+
availableRepos: mockRepos
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Set environment variables to simulate CLI discovery
|
|
81
|
+
process.env.AVAILABLE_REPOSITORIES = JSON.stringify(mockRepos)
|
|
82
|
+
process.env.REPOSITORY_INFO = JSON.stringify(mockCurrentRepo)
|
|
83
|
+
|
|
84
|
+
const response = await request(app)
|
|
85
|
+
.get('/api/project/repositories')
|
|
86
|
+
.expect(200)
|
|
87
|
+
|
|
88
|
+
expect(response.body).toEqual({
|
|
89
|
+
success: true,
|
|
90
|
+
data: {
|
|
91
|
+
repositories: mockRepos,
|
|
92
|
+
currentRepository: mockCurrentRepo,
|
|
93
|
+
isMultiRepo: true
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should fall back to CLI discovery when no environment variable', async () => {
|
|
99
|
+
const { exec } = await import('child_process')
|
|
100
|
+
const { promisify } = await import('util')
|
|
101
|
+
|
|
102
|
+
const mockRepos = [
|
|
103
|
+
{
|
|
104
|
+
name: 'discovered-app',
|
|
105
|
+
path: '/discovered/path',
|
|
106
|
+
framework: 'Angular'
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
// Mock execAsync to return discovered repositories
|
|
111
|
+
const execAsync = vi.fn().mockResolvedValue({
|
|
112
|
+
stdout: JSON.stringify(mockRepos),
|
|
113
|
+
stderr: ''
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
vi.mocked(promisify).mockReturnValue(execAsync)
|
|
117
|
+
|
|
118
|
+
const response = await request(app)
|
|
119
|
+
.get('/api/project/repositories')
|
|
120
|
+
.expect(200)
|
|
121
|
+
|
|
122
|
+
expect(response.body.data.repositories).toEqual(mockRepos)
|
|
123
|
+
expect(response.body.data.isMultiRepo).toBe(false)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should handle CLI discovery errors gracefully', async () => {
|
|
127
|
+
const { promisify } = await import('util')
|
|
128
|
+
|
|
129
|
+
// Mock execAsync to throw error
|
|
130
|
+
const execAsync = vi.fn().mockRejectedValue(new Error('CLI discovery failed'))
|
|
131
|
+
vi.mocked(promisify).mockReturnValue(execAsync)
|
|
132
|
+
|
|
133
|
+
const response = await request(app)
|
|
134
|
+
.get('/api/project/repositories')
|
|
135
|
+
.expect(200)
|
|
136
|
+
|
|
137
|
+
expect(response.body).toEqual({
|
|
138
|
+
success: true,
|
|
139
|
+
data: {
|
|
140
|
+
repositories: [],
|
|
141
|
+
currentRepository: null,
|
|
142
|
+
isMultiRepo: false,
|
|
143
|
+
error: 'Failed to discover repositories: CLI discovery failed'
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should handle malformed environment variable gracefully', async () => {
|
|
149
|
+
// Set malformed JSON in environment variable
|
|
150
|
+
process.env.AVAILABLE_REPOSITORIES = 'invalid-json'
|
|
151
|
+
|
|
152
|
+
const { promisify } = await import('util')
|
|
153
|
+
const execAsync = vi.fn().mockResolvedValue({
|
|
154
|
+
stdout: '[]',
|
|
155
|
+
stderr: ''
|
|
156
|
+
})
|
|
157
|
+
vi.mocked(promisify).mockReturnValue(execAsync)
|
|
158
|
+
|
|
159
|
+
const response = await request(app)
|
|
160
|
+
.get('/api/project/repositories')
|
|
161
|
+
.expect(200)
|
|
162
|
+
|
|
163
|
+
// Should fall back to CLI discovery
|
|
164
|
+
expect(response.body.data.repositories).toEqual([])
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should include current repository information', async () => {
|
|
168
|
+
const mockCurrentRepo = {
|
|
169
|
+
name: 'current-app',
|
|
170
|
+
path: '/current/path',
|
|
171
|
+
version: '1.5.0',
|
|
172
|
+
isMultiRepo: false
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
process.env.REPOSITORY_INFO = JSON.stringify(mockCurrentRepo)
|
|
176
|
+
process.env.AVAILABLE_REPOSITORIES = JSON.stringify([])
|
|
177
|
+
|
|
178
|
+
const response = await request(app)
|
|
179
|
+
.get('/api/project/repositories')
|
|
180
|
+
.expect(200)
|
|
181
|
+
|
|
182
|
+
expect(response.body.data.currentRepository).toEqual(mockCurrentRepo)
|
|
183
|
+
expect(response.body.data.isMultiRepo).toBe(false)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should detect multi-repo scenario correctly', async () => {
|
|
187
|
+
const mockRepos = [
|
|
188
|
+
{ name: 'app1', path: '/path1' },
|
|
189
|
+
{ name: 'app2', path: '/path2' },
|
|
190
|
+
{ name: 'app3', path: '/path3' }
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
const mockCurrentRepo = {
|
|
194
|
+
name: 'Multiple Repositories Available',
|
|
195
|
+
isMultiRepo: true,
|
|
196
|
+
availableRepos: mockRepos
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
process.env.AVAILABLE_REPOSITORIES = JSON.stringify(mockRepos)
|
|
200
|
+
process.env.REPOSITORY_INFO = JSON.stringify(mockCurrentRepo)
|
|
201
|
+
|
|
202
|
+
const response = await request(app)
|
|
203
|
+
.get('/api/project/repositories')
|
|
204
|
+
.expect(200)
|
|
205
|
+
|
|
206
|
+
expect(response.body.data.isMultiRepo).toBe(true)
|
|
207
|
+
expect(response.body.data.repositories).toHaveLength(3)
|
|
208
|
+
expect(response.body.data.currentRepository.isMultiRepo).toBe(true)
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
describe('POST /api/project/switch-repository', () => {
|
|
213
|
+
it('should switch repository successfully', async () => {
|
|
214
|
+
const fs = await import('fs/promises')
|
|
215
|
+
|
|
216
|
+
// Mock file system operations
|
|
217
|
+
vi.mocked(fs.stat).mockResolvedValue({ isDirectory: () => true })
|
|
218
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({
|
|
219
|
+
name: 'switched-app',
|
|
220
|
+
version: '2.0.0'
|
|
221
|
+
}))
|
|
222
|
+
|
|
223
|
+
// Mock WebSocket
|
|
224
|
+
const mockIo = {
|
|
225
|
+
emit: vi.fn()
|
|
226
|
+
}
|
|
227
|
+
app.set('io', mockIo)
|
|
228
|
+
|
|
229
|
+
const response = await request(app)
|
|
230
|
+
.post('/api/project/switch-repository')
|
|
231
|
+
.send({ repositoryPath: '/path/to/new/repo' })
|
|
232
|
+
.expect(200)
|
|
233
|
+
|
|
234
|
+
expect(response.body.success).toBe(true)
|
|
235
|
+
expect(response.body.data.repository.name).toBe('switched-app')
|
|
236
|
+
expect(response.body.data.repository.path).toBe('/path/to/new/repo')
|
|
237
|
+
|
|
238
|
+
// Should emit WebSocket event
|
|
239
|
+
expect(mockIo.emit).toHaveBeenCalledWith('repository:switched', {
|
|
240
|
+
repository: {
|
|
241
|
+
name: 'switched-app',
|
|
242
|
+
path: '/path/to/new/repo',
|
|
243
|
+
version: '2.0.0'
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should reject invalid repository path', async () => {
|
|
249
|
+
const response = await request(app)
|
|
250
|
+
.post('/api/project/switch-repository')
|
|
251
|
+
.send({}) // Missing repositoryPath
|
|
252
|
+
.expect(400)
|
|
253
|
+
|
|
254
|
+
expect(response.body.success).toBe(false)
|
|
255
|
+
expect(response.body.error.code).toBe('VALIDATION_ERROR')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('should handle non-existent repository path', async () => {
|
|
259
|
+
const fs = await import('fs/promises')
|
|
260
|
+
|
|
261
|
+
// Mock file system to throw error
|
|
262
|
+
vi.mocked(fs.stat).mockRejectedValue(new Error('Path not found'))
|
|
263
|
+
|
|
264
|
+
const response = await request(app)
|
|
265
|
+
.post('/api/project/switch-repository')
|
|
266
|
+
.send({ repositoryPath: '/invalid/path' })
|
|
267
|
+
.expect(500)
|
|
268
|
+
|
|
269
|
+
expect(response.body.success).toBe(false)
|
|
270
|
+
expect(response.body.error.code).toBe('INTERNAL_ERROR')
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
})
|