@friggframework/devtools 2.0.0-next.4 → 2.0.0-next.40
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/build-command/index.js +54 -0
- package/frigg-cli/deploy-command/index.js +175 -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/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +47 -1
- package/frigg-cli/index.test.js +1 -4
- 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/install-command/index.js +1 -4
- package/frigg-cli/package.json +51 -0
- package/frigg-cli/start-command/index.js +24 -4
- 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 +16 -17
- 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/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +278 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
- package/infrastructure/README.md +443 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/aws-discovery.js +1176 -0
- package/infrastructure/aws-discovery.test.js +1220 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +378 -0
- package/infrastructure/create-frigg-infrastructure.js +3 -5
- package/infrastructure/env-validator.js +77 -0
- package/infrastructure/frigg-deployment-iam-stack.yaml +401 -0
- package/infrastructure/iam-generator.js +836 -0
- package/infrastructure/iam-generator.test.js +172 -0
- package/infrastructure/iam-policy-basic.json +218 -0
- package/infrastructure/iam-policy-full.json +288 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +1472 -138
- package/infrastructure/serverless-template.test.js +1759 -0
- 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/index.html +13 -0
- 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/assets/FriggLogo.svg +1 -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 +20 -9
- package/infrastructure/app-handler-helpers.js +0 -57
- package/infrastructure/backend-utils.js +0 -90
- package/infrastructure/routers/auth.js +0 -26
- package/infrastructure/routers/integration-defined-routers.js +0 -37
- package/infrastructure/routers/middleware/loadUser.js +0 -15
- package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
- package/infrastructure/routers/user.js +0 -41
- package/infrastructure/routers/websocket.js +0 -55
- package/infrastructure/workers/integration-defined-workers.js +0 -24
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import fetch from 'node-fetch'
|
|
3
|
+
import { createStandardResponse } from '../utils/response.js'
|
|
4
|
+
|
|
5
|
+
const router = express.Router()
|
|
6
|
+
|
|
7
|
+
// Get real integrations from NPM registry
|
|
8
|
+
async function fetchRealIntegrations() {
|
|
9
|
+
try {
|
|
10
|
+
const searchUrl = 'https://registry.npmjs.org/-/v1/search?text=@friggframework%20api-module&size=100';
|
|
11
|
+
|
|
12
|
+
const response = await fetch(searchUrl);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`NPM search failed: ${response.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const data = await response.json();
|
|
18
|
+
|
|
19
|
+
return data.objects
|
|
20
|
+
.filter(pkg => pkg.package.name.includes('@friggframework/api-module-'))
|
|
21
|
+
.map(pkg => ({
|
|
22
|
+
id: pkg.package.name.replace('@friggframework/api-module-', ''),
|
|
23
|
+
name: pkg.package.name.replace('@friggframework/api-module-', '').replace('-', ' ').split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
|
|
24
|
+
description: pkg.package.description || 'No description available',
|
|
25
|
+
category: detectCategory(pkg.package.name, pkg.package.description || '', pkg.package.keywords || []),
|
|
26
|
+
status: 'available',
|
|
27
|
+
installed: false,
|
|
28
|
+
version: pkg.package.version,
|
|
29
|
+
packageName: pkg.package.name
|
|
30
|
+
}));
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Error fetching real integrations:', error);
|
|
33
|
+
// Fallback to basic integrations
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
id: 'hubspot',
|
|
37
|
+
name: 'HubSpot',
|
|
38
|
+
description: 'CRM and marketing platform integration',
|
|
39
|
+
category: 'crm',
|
|
40
|
+
status: 'available',
|
|
41
|
+
installed: false,
|
|
42
|
+
version: '2.0.0',
|
|
43
|
+
packageName: '@friggframework/api-module-hubspot'
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Helper to detect integration category
|
|
50
|
+
function detectCategory(name, description, keywords) {
|
|
51
|
+
const text = `${name} ${description} ${keywords.join(' ')}`.toLowerCase();
|
|
52
|
+
|
|
53
|
+
const categoryPatterns = {
|
|
54
|
+
'crm': ['crm', 'customer', 'salesforce', 'hubspot', 'pipedrive'],
|
|
55
|
+
'communication': ['email', 'sms', 'chat', 'slack', 'discord', 'teams'],
|
|
56
|
+
'marketing': ['marketing', 'campaign', 'mailchimp', 'activecampaign'],
|
|
57
|
+
'productivity': ['task', 'project', 'asana', 'trello', 'notion', 'jira'],
|
|
58
|
+
'support': ['support', 'helpdesk', 'ticket', 'zendesk', 'intercom'],
|
|
59
|
+
'finance': ['accounting', 'invoice', 'quickbooks', 'xero', 'billing']
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (const [category, patterns] of Object.entries(categoryPatterns)) {
|
|
63
|
+
for (const pattern of patterns) {
|
|
64
|
+
if (text.includes(pattern)) {
|
|
65
|
+
return category;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return 'other';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Get integration categories
|
|
74
|
+
router.get('/categories', async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const integrations = await fetchRealIntegrations();
|
|
77
|
+
|
|
78
|
+
// Count integrations by category
|
|
79
|
+
const categoryCounts = integrations.reduce((acc, integration) => {
|
|
80
|
+
const category = integration.category;
|
|
81
|
+
acc[category] = (acc[category] || 0) + 1;
|
|
82
|
+
return acc;
|
|
83
|
+
}, {});
|
|
84
|
+
|
|
85
|
+
const categories = Object.entries(categoryCounts).map(([id, count]) => ({
|
|
86
|
+
id,
|
|
87
|
+
name: id.charAt(0).toUpperCase() + id.slice(1),
|
|
88
|
+
count
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
res.json({
|
|
92
|
+
status: 'success',
|
|
93
|
+
data: categories
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Error fetching categories:', error);
|
|
97
|
+
res.status(500).json({
|
|
98
|
+
status: 'error',
|
|
99
|
+
message: 'Failed to fetch categories'
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Get all integrations
|
|
105
|
+
router.get('/integrations', async (req, res) => {
|
|
106
|
+
try {
|
|
107
|
+
const { category, status, installed } = req.query;
|
|
108
|
+
|
|
109
|
+
let integrations = await fetchRealIntegrations();
|
|
110
|
+
|
|
111
|
+
// Filter by category
|
|
112
|
+
if (category && category !== 'all') {
|
|
113
|
+
integrations = integrations.filter(i => i.category === category);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Filter by status
|
|
117
|
+
if (status) {
|
|
118
|
+
integrations = integrations.filter(i => i.status === status);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Filter by installed
|
|
122
|
+
if (installed !== undefined) {
|
|
123
|
+
integrations = integrations.filter(i => i.installed === (installed === 'true'));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
res.json({
|
|
127
|
+
status: 'success',
|
|
128
|
+
data: {
|
|
129
|
+
integrations,
|
|
130
|
+
total: integrations.length
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('Error fetching integrations:', error);
|
|
135
|
+
res.status(500).json({
|
|
136
|
+
status: 'error',
|
|
137
|
+
message: 'Failed to fetch integrations'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Get installed integrations
|
|
143
|
+
router.get('/installed', async (req, res) => {
|
|
144
|
+
try {
|
|
145
|
+
// Import the integration detection logic
|
|
146
|
+
const { getInstalledIntegrations } = await import('./integrations.js');
|
|
147
|
+
const installedIntegrations = await getInstalledIntegrations();
|
|
148
|
+
|
|
149
|
+
// Format for discovery API
|
|
150
|
+
const formatted = installedIntegrations.map(integration => ({
|
|
151
|
+
id: integration.name.toLowerCase(),
|
|
152
|
+
name: integration.name,
|
|
153
|
+
description: integration.description,
|
|
154
|
+
category: integration.category,
|
|
155
|
+
status: 'installed',
|
|
156
|
+
installed: true,
|
|
157
|
+
version: '1.0.0' // We don't have version info for actual integrations
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
res.json({
|
|
161
|
+
status: 'success',
|
|
162
|
+
data: formatted
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('Error fetching installed integrations:', error);
|
|
166
|
+
res.status(500).json({
|
|
167
|
+
status: 'error',
|
|
168
|
+
message: 'Failed to fetch installed integrations'
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// Clear discovery cache
|
|
174
|
+
router.post('/cache/clear', (req, res) => {
|
|
175
|
+
// In a real implementation, this would clear actual cache
|
|
176
|
+
res.json({
|
|
177
|
+
status: 'success',
|
|
178
|
+
data: {
|
|
179
|
+
message: 'Discovery cache cleared successfully',
|
|
180
|
+
timestamp: new Date().toISOString()
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
export default router
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as environmentRouter } from './router.js';
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import EnvFileManager from '../../utils/environment/envFileManager.js';
|
|
4
|
+
import AWSParameterStore from '../../utils/environment/awsParameterStore.js';
|
|
5
|
+
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
|
|
8
|
+
// Initialize managers
|
|
9
|
+
const projectRoot = process.env.PROJECT_ROOT || path.resolve(process.cwd(), '../../../');
|
|
10
|
+
const envManager = new EnvFileManager(projectRoot);
|
|
11
|
+
const awsManager = new AWSParameterStore({
|
|
12
|
+
prefix: process.env.AWS_PARAMETER_PREFIX || '/frigg',
|
|
13
|
+
region: process.env.AWS_REGION || 'us-east-1'
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Middleware to validate environment parameter
|
|
17
|
+
const validateEnvironment = (req, res, next) => {
|
|
18
|
+
const validEnvironments = ['local', 'staging', 'production'];
|
|
19
|
+
const { environment } = req.params;
|
|
20
|
+
|
|
21
|
+
if (!validEnvironments.includes(environment)) {
|
|
22
|
+
return res.status(400).json({
|
|
23
|
+
error: 'Invalid environment',
|
|
24
|
+
validEnvironments
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
next();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// GET /api/environment/variables/:environment
|
|
32
|
+
router.get('/variables/:environment', validateEnvironment, async (req, res) => {
|
|
33
|
+
try {
|
|
34
|
+
const { environment } = req.params;
|
|
35
|
+
const { includeAws } = req.query;
|
|
36
|
+
|
|
37
|
+
// Get variables from .env file
|
|
38
|
+
const fileVariables = await envManager.readEnvFile(environment);
|
|
39
|
+
|
|
40
|
+
let variables = fileVariables;
|
|
41
|
+
|
|
42
|
+
// Merge with AWS if requested and environment is production
|
|
43
|
+
if (includeAws === 'true' && environment === 'production') {
|
|
44
|
+
try {
|
|
45
|
+
const awsVariables = await awsManager.getParameters(environment);
|
|
46
|
+
variables = envManager.mergeVariables(fileVariables, awsVariables, true);
|
|
47
|
+
} catch (awsError) {
|
|
48
|
+
console.error('AWS fetch error:', awsError);
|
|
49
|
+
// Continue with file variables only
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
res.json({
|
|
54
|
+
environment,
|
|
55
|
+
variables,
|
|
56
|
+
source: includeAws === 'true' ? 'merged' : 'file'
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('Error fetching variables:', error);
|
|
60
|
+
res.status(500).json({
|
|
61
|
+
error: 'Failed to fetch environment variables',
|
|
62
|
+
message: error.message
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// PUT /api/environment/variables/:environment
|
|
68
|
+
router.put('/variables/:environment', validateEnvironment, async (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
const { environment } = req.params;
|
|
71
|
+
const { variables } = req.body;
|
|
72
|
+
|
|
73
|
+
if (!Array.isArray(variables)) {
|
|
74
|
+
return res.status(400).json({
|
|
75
|
+
error: 'Variables must be an array'
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Validate variables
|
|
80
|
+
const errors = envManager.validateVariables(variables);
|
|
81
|
+
if (errors.length > 0) {
|
|
82
|
+
return res.status(400).json({
|
|
83
|
+
error: 'Validation errors',
|
|
84
|
+
errors
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Write to file
|
|
89
|
+
const result = await envManager.writeEnvFile(environment, variables);
|
|
90
|
+
|
|
91
|
+
res.json({
|
|
92
|
+
success: true,
|
|
93
|
+
environment,
|
|
94
|
+
count: variables.length,
|
|
95
|
+
...result
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Error saving variables:', error);
|
|
99
|
+
res.status(500).json({
|
|
100
|
+
error: 'Failed to save environment variables',
|
|
101
|
+
message: error.message
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// POST /api/environment/sync/aws-parameter-store
|
|
107
|
+
router.post('/sync/aws-parameter-store', async (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
const { environment } = req.body;
|
|
110
|
+
|
|
111
|
+
if (environment !== 'production') {
|
|
112
|
+
return res.status(400).json({
|
|
113
|
+
error: 'AWS sync is only available for production environment'
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Validate AWS access
|
|
118
|
+
const accessCheck = await awsManager.validateAccess();
|
|
119
|
+
if (!accessCheck.valid) {
|
|
120
|
+
return res.status(403).json({
|
|
121
|
+
error: 'AWS access denied',
|
|
122
|
+
message: accessCheck.error,
|
|
123
|
+
code: accessCheck.code
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Get variables from file
|
|
128
|
+
const fileVariables = await envManager.readEnvFile(environment);
|
|
129
|
+
|
|
130
|
+
// Sync to AWS
|
|
131
|
+
const syncResult = await awsManager.syncEnvironment(environment, fileVariables);
|
|
132
|
+
|
|
133
|
+
res.json({
|
|
134
|
+
success: true,
|
|
135
|
+
environment,
|
|
136
|
+
count: fileVariables.length,
|
|
137
|
+
...syncResult
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Error syncing to AWS:', error);
|
|
141
|
+
res.status(500).json({
|
|
142
|
+
error: 'Failed to sync with AWS Parameter Store',
|
|
143
|
+
message: error.message
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// GET /api/environment/export/:environment
|
|
149
|
+
router.get('/export/:environment', validateEnvironment, async (req, res) => {
|
|
150
|
+
try {
|
|
151
|
+
const { environment } = req.params;
|
|
152
|
+
const { format, excludeSecrets } = req.query;
|
|
153
|
+
|
|
154
|
+
const variables = await envManager.readEnvFile(environment);
|
|
155
|
+
|
|
156
|
+
let exportData;
|
|
157
|
+
let contentType;
|
|
158
|
+
let filename;
|
|
159
|
+
|
|
160
|
+
if (format === 'json') {
|
|
161
|
+
// Export as JSON
|
|
162
|
+
const data = {};
|
|
163
|
+
variables.forEach(v => {
|
|
164
|
+
if (!excludeSecrets || !v.isSecret) {
|
|
165
|
+
data[v.key] = v.value;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
exportData = JSON.stringify(data, null, 2);
|
|
170
|
+
contentType = 'application/json';
|
|
171
|
+
filename = `${environment}-env.json`;
|
|
172
|
+
} else {
|
|
173
|
+
// Export as .env format
|
|
174
|
+
let content = `# Environment: ${environment}\n`;
|
|
175
|
+
content += `# Exported: ${new Date().toISOString()}\n\n`;
|
|
176
|
+
|
|
177
|
+
const sorted = variables.sort((a, b) => a.key.localeCompare(b.key));
|
|
178
|
+
|
|
179
|
+
for (const v of sorted) {
|
|
180
|
+
if (v.description) {
|
|
181
|
+
content += `# ${v.description}\n`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const value = (excludeSecrets === 'true' && v.isSecret) ? '**REDACTED**' : v.value;
|
|
185
|
+
content += `${v.key}=${envManager.escapeValue(value)}\n\n`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
exportData = content.trim();
|
|
189
|
+
contentType = 'text/plain';
|
|
190
|
+
filename = environment === 'local' ? '.env' : `.env.${environment}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
res.setHeader('Content-Type', contentType);
|
|
194
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
195
|
+
res.send(exportData);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error('Error exporting variables:', error);
|
|
198
|
+
res.status(500).json({
|
|
199
|
+
error: 'Failed to export environment variables',
|
|
200
|
+
message: error.message
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// POST /api/environment/import/:environment
|
|
206
|
+
router.post('/import/:environment', validateEnvironment, async (req, res) => {
|
|
207
|
+
try {
|
|
208
|
+
const { environment } = req.params;
|
|
209
|
+
const { data, format, merge } = req.body;
|
|
210
|
+
|
|
211
|
+
if (!data) {
|
|
212
|
+
return res.status(400).json({
|
|
213
|
+
error: 'No data provided for import'
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let importedVariables = [];
|
|
218
|
+
|
|
219
|
+
if (format === 'json') {
|
|
220
|
+
// Import from JSON
|
|
221
|
+
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
222
|
+
importedVariables = Object.entries(parsed).map(([key, value]) => ({
|
|
223
|
+
id: `${environment}-${key}-${Date.now()}`,
|
|
224
|
+
key,
|
|
225
|
+
value: String(value),
|
|
226
|
+
description: '',
|
|
227
|
+
isSecret: envManager.isSecretVariable(key),
|
|
228
|
+
environment
|
|
229
|
+
}));
|
|
230
|
+
} else {
|
|
231
|
+
// Import from .env format
|
|
232
|
+
const lines = data.split('\n');
|
|
233
|
+
let currentDescription = '';
|
|
234
|
+
|
|
235
|
+
for (const line of lines) {
|
|
236
|
+
const trimmed = line.trim();
|
|
237
|
+
|
|
238
|
+
if (trimmed.startsWith('#')) {
|
|
239
|
+
currentDescription = trimmed.substring(1).trim();
|
|
240
|
+
} else if (trimmed && trimmed.includes('=')) {
|
|
241
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
242
|
+
const value = valueParts.join('=').replace(/^["']|["']$/g, '');
|
|
243
|
+
|
|
244
|
+
importedVariables.push({
|
|
245
|
+
id: `${environment}-${key}-${Date.now()}`,
|
|
246
|
+
key: key.trim(),
|
|
247
|
+
value,
|
|
248
|
+
description: currentDescription,
|
|
249
|
+
isSecret: envManager.isSecretVariable(key),
|
|
250
|
+
environment
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
currentDescription = '';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Validate imported variables
|
|
259
|
+
const errors = envManager.validateVariables(importedVariables);
|
|
260
|
+
if (errors.length > 0) {
|
|
261
|
+
return res.status(400).json({
|
|
262
|
+
error: 'Validation errors in imported data',
|
|
263
|
+
errors
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let finalVariables = importedVariables;
|
|
268
|
+
|
|
269
|
+
// Merge with existing if requested
|
|
270
|
+
if (merge === 'true') {
|
|
271
|
+
const existing = await envManager.readEnvFile(environment);
|
|
272
|
+
const merged = [...existing];
|
|
273
|
+
|
|
274
|
+
importedVariables.forEach(importVar => {
|
|
275
|
+
const existingIndex = merged.findIndex(v => v.key === importVar.key);
|
|
276
|
+
if (existingIndex >= 0) {
|
|
277
|
+
merged[existingIndex] = { ...merged[existingIndex], value: importVar.value };
|
|
278
|
+
} else {
|
|
279
|
+
merged.push(importVar);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
finalVariables = merged;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Save variables
|
|
287
|
+
await envManager.writeEnvFile(environment, finalVariables);
|
|
288
|
+
|
|
289
|
+
res.json({
|
|
290
|
+
success: true,
|
|
291
|
+
environment,
|
|
292
|
+
imported: importedVariables.length,
|
|
293
|
+
total: finalVariables.length
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('Error importing variables:', error);
|
|
297
|
+
res.status(500).json({
|
|
298
|
+
error: 'Failed to import environment variables',
|
|
299
|
+
message: error.message
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// POST /api/environment/copy
|
|
305
|
+
router.post('/copy', async (req, res) => {
|
|
306
|
+
try {
|
|
307
|
+
const { source, target, excludeSecrets } = req.body;
|
|
308
|
+
|
|
309
|
+
if (!source || !target) {
|
|
310
|
+
return res.status(400).json({
|
|
311
|
+
error: 'Source and target environments are required'
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const copiedVariables = await envManager.copyEnvironment(
|
|
316
|
+
source,
|
|
317
|
+
target,
|
|
318
|
+
excludeSecrets === 'true'
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
res.json({
|
|
322
|
+
success: true,
|
|
323
|
+
source,
|
|
324
|
+
target,
|
|
325
|
+
count: copiedVariables.length,
|
|
326
|
+
excludedSecrets: excludeSecrets === 'true'
|
|
327
|
+
});
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error('Error copying environment:', error);
|
|
330
|
+
res.status(500).json({
|
|
331
|
+
error: 'Failed to copy environment',
|
|
332
|
+
message: error.message
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// GET /api/environment/aws/validate
|
|
338
|
+
router.get('/aws/validate', async (req, res) => {
|
|
339
|
+
try {
|
|
340
|
+
const validation = await awsManager.validateAccess();
|
|
341
|
+
res.json(validation);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.error('Error validating AWS access:', error);
|
|
344
|
+
res.status(500).json({
|
|
345
|
+
error: 'Failed to validate AWS access',
|
|
346
|
+
message: error.message
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// GET /api/environment/history/:environment/:key
|
|
352
|
+
router.get('/history/:environment/:key', validateEnvironment, async (req, res) => {
|
|
353
|
+
try {
|
|
354
|
+
const { environment, key } = req.params;
|
|
355
|
+
|
|
356
|
+
if (environment !== 'production') {
|
|
357
|
+
return res.status(400).json({
|
|
358
|
+
error: 'History is only available for production environment (AWS)'
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const history = await awsManager.getParameterHistory(environment, key);
|
|
363
|
+
|
|
364
|
+
res.json({
|
|
365
|
+
environment,
|
|
366
|
+
key,
|
|
367
|
+
history
|
|
368
|
+
});
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error('Error fetching parameter history:', error);
|
|
371
|
+
res.status(500).json({
|
|
372
|
+
error: 'Failed to fetch parameter history',
|
|
373
|
+
message: error.message
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
export default router;
|