@friggframework/devtools 2.0.0--canary.398.7664c46.0 → 2.0.0--canary.400.bed3308.0
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 +15 -2
- package/frigg-cli/deploy-command/index.js +15 -2
- 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 +350 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/index.js +66 -4
- package/frigg-cli/install-command/index.js +15 -2
- package/frigg-cli/package.json +75 -0
- package/frigg-cli/start-command/index.js +17 -2
- package/frigg-cli/ui-command/index.js +167 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +38 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +43 -19
- package/infrastructure/IAM-POLICY-TEMPLATES.md +1 -1
- package/infrastructure/frigg-deployment-iam-stack.yaml +16 -2
- package/infrastructure/iam-generator.js +129 -6
- package/infrastructure/iam-policy-basic.json +29 -5
- package/infrastructure/iam-policy-full.json +28 -5
- package/infrastructure/serverless-template.js +209 -3
- package/infrastructure/serverless-template.test.js +12 -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/{dist/index.html → index.html} +1 -2
- package/management-ui/merge-conflict-cleaner.py +371 -0
- package/management-ui/package-lock.json +10997 -0
- package/management-ui/package.json +76 -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 +479 -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 +553 -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 +428 -0
- package/management-ui/server/middleware/errorHandler.js +70 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +188 -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 +51 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +2 -0
- package/management-ui/src/components/Card.jsx +9 -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 +199 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +490 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +235 -0
- package/management-ui/src/components/Layout.jsx +250 -0
- package/management-ui/src/components/LoadingSpinner.jsx +45 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +255 -0
- package/management-ui/src/components/StatusBadge.jsx +70 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +154 -0
- package/management-ui/src/components/UserSimulation.jsx +299 -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 +387 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +194 -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 +427 -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 +544 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +479 -0
- package/management-ui/src/pages/IntegrationTest.jsx +494 -0
- package/management-ui/src/pages/Integrations.jsx +254 -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-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,553 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import fs from 'fs/promises'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
// import { analyzeIntegrations } from '../../../frigg-cli/utils/integration-analyzer.js'
|
|
5
|
+
import { createStandardResponse, createErrorResponse, ERROR_CODES, asyncHandler } from '../utils/response.js'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
7
|
+
import { promisify } from 'util'
|
|
8
|
+
import { spawn, exec } from 'child_process'
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
+
const __dirname = path.dirname(__filename)
|
|
12
|
+
const execAsync = promisify(exec)
|
|
13
|
+
const router = express.Router()
|
|
14
|
+
|
|
15
|
+
// Track project process state
|
|
16
|
+
let projectProcess = null
|
|
17
|
+
let projectStatus = 'stopped'
|
|
18
|
+
let projectLogs = []
|
|
19
|
+
let projectStartTime = null
|
|
20
|
+
const MAX_LOGS = 1000
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get current project status and configuration
|
|
24
|
+
*/
|
|
25
|
+
router.get('/status', asyncHandler(async (req, res) => {
|
|
26
|
+
const cwd = process.cwd()
|
|
27
|
+
let projectInfo = {
|
|
28
|
+
name: 'unknown',
|
|
29
|
+
version: '0.0.0',
|
|
30
|
+
friggVersion: 'unknown'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Try to read package.json for project info
|
|
35
|
+
const packageJsonPath = path.join(cwd, 'package.json')
|
|
36
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
|
|
37
|
+
|
|
38
|
+
projectInfo = {
|
|
39
|
+
name: packageJson.name || 'frigg-project',
|
|
40
|
+
version: packageJson.version || '0.0.0',
|
|
41
|
+
friggVersion: packageJson.dependencies?.['@friggframework/core'] ||
|
|
42
|
+
packageJson.devDependencies?.['@friggframework/core'] || 'unknown'
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.warn('Could not read package.json:', error.message)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const statusData = {
|
|
49
|
+
...projectInfo,
|
|
50
|
+
status: projectStatus,
|
|
51
|
+
pid: projectProcess?.pid || null,
|
|
52
|
+
uptime: projectStartTime ? Math.floor((Date.now() - projectStartTime) / 1000) : 0,
|
|
53
|
+
port: process.env.PORT || 3000,
|
|
54
|
+
environment: process.env.NODE_ENV || 'development',
|
|
55
|
+
lastStarted: projectStartTime ? new Date(projectStartTime).toISOString() : null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
res.json(createStandardResponse(statusData))
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Start the Frigg project
|
|
63
|
+
*/
|
|
64
|
+
router.post('/start', asyncHandler(async (req, res) => {
|
|
65
|
+
if (projectProcess && projectStatus === 'running') {
|
|
66
|
+
return res.status(400).json(
|
|
67
|
+
createErrorResponse(ERROR_CODES.PROJECT_ALREADY_RUNNING, 'Project is already running')
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { stage = 'dev', verbose = false, port = 3000 } = req.body
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
projectStatus = 'starting'
|
|
75
|
+
projectLogs = []
|
|
76
|
+
|
|
77
|
+
// Broadcast status update via WebSocket
|
|
78
|
+
const io = req.app.get('io')
|
|
79
|
+
if (io) {
|
|
80
|
+
io.emit('project:status', {
|
|
81
|
+
status: 'starting',
|
|
82
|
+
message: 'Starting Frigg project...'
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Find the project directory (current working directory)
|
|
87
|
+
const projectPath = process.cwd()
|
|
88
|
+
|
|
89
|
+
// Build command arguments
|
|
90
|
+
const args = ['run', 'start']
|
|
91
|
+
if (stage !== 'dev') {
|
|
92
|
+
args.push('--', '--stage', stage)
|
|
93
|
+
}
|
|
94
|
+
if (verbose) {
|
|
95
|
+
args.push('--', '--verbose')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Set environment variables
|
|
99
|
+
const env = {
|
|
100
|
+
...process.env,
|
|
101
|
+
NODE_ENV: stage === 'production' ? 'production' : 'development',
|
|
102
|
+
PORT: port.toString()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Start the project process
|
|
106
|
+
projectProcess = spawn('npm', args, {
|
|
107
|
+
cwd: projectPath,
|
|
108
|
+
env,
|
|
109
|
+
shell: true,
|
|
110
|
+
detached: false
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
projectStartTime = Date.now()
|
|
114
|
+
|
|
115
|
+
// Handle stdout
|
|
116
|
+
projectProcess.stdout?.on('data', (data) => {
|
|
117
|
+
const log = {
|
|
118
|
+
type: 'stdout',
|
|
119
|
+
message: data.toString(),
|
|
120
|
+
timestamp: new Date().toISOString()
|
|
121
|
+
}
|
|
122
|
+
projectLogs.push(log)
|
|
123
|
+
if (projectLogs.length > MAX_LOGS) {
|
|
124
|
+
projectLogs.shift()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Broadcast log via WebSocket
|
|
128
|
+
if (io) {
|
|
129
|
+
io.emit('project:logs', log)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Handle stderr
|
|
134
|
+
projectProcess.stderr?.on('data', (data) => {
|
|
135
|
+
const log = {
|
|
136
|
+
type: 'stderr',
|
|
137
|
+
message: data.toString(),
|
|
138
|
+
timestamp: new Date().toISOString()
|
|
139
|
+
}
|
|
140
|
+
projectLogs.push(log)
|
|
141
|
+
if (projectLogs.length > MAX_LOGS) {
|
|
142
|
+
projectLogs.shift()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Broadcast log via WebSocket
|
|
146
|
+
if (io) {
|
|
147
|
+
io.emit('project:logs', log)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Handle process exit
|
|
152
|
+
projectProcess.on('exit', (code, signal) => {
|
|
153
|
+
const wasRunning = projectStatus === 'running'
|
|
154
|
+
projectStatus = 'stopped'
|
|
155
|
+
projectProcess = null
|
|
156
|
+
projectStartTime = null
|
|
157
|
+
|
|
158
|
+
const statusUpdate = {
|
|
159
|
+
status: 'stopped',
|
|
160
|
+
code,
|
|
161
|
+
signal,
|
|
162
|
+
message: `Project process exited with code ${code}`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (io) {
|
|
166
|
+
io.emit('project:status', statusUpdate)
|
|
167
|
+
if (wasRunning) {
|
|
168
|
+
io.emit('project:error', {
|
|
169
|
+
message: 'Project stopped unexpectedly',
|
|
170
|
+
code,
|
|
171
|
+
signal
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Handle process errors
|
|
178
|
+
projectProcess.on('error', (error) => {
|
|
179
|
+
projectStatus = 'stopped'
|
|
180
|
+
projectProcess = null
|
|
181
|
+
projectStartTime = null
|
|
182
|
+
|
|
183
|
+
if (io) {
|
|
184
|
+
io.emit('project:error', {
|
|
185
|
+
message: 'Failed to start project',
|
|
186
|
+
error: error.message
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// Wait for process to stabilize
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
193
|
+
|
|
194
|
+
if (projectProcess && !projectProcess.killed) {
|
|
195
|
+
projectStatus = 'running'
|
|
196
|
+
|
|
197
|
+
if (io) {
|
|
198
|
+
io.emit('project:status', {
|
|
199
|
+
status: 'running',
|
|
200
|
+
message: 'Project started successfully',
|
|
201
|
+
pid: projectProcess.pid
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
res.json(createStandardResponse({
|
|
206
|
+
message: 'Project started successfully',
|
|
207
|
+
pid: projectProcess.pid,
|
|
208
|
+
status: 'running'
|
|
209
|
+
}))
|
|
210
|
+
} else {
|
|
211
|
+
throw new Error('Failed to start project process')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
} catch (error) {
|
|
215
|
+
projectStatus = 'stopped'
|
|
216
|
+
projectProcess = null
|
|
217
|
+
projectStartTime = null
|
|
218
|
+
|
|
219
|
+
const io = req.app.get('io')
|
|
220
|
+
if (io) {
|
|
221
|
+
io.emit('project:status', { status: 'stopped' })
|
|
222
|
+
io.emit('project:error', { message: error.message })
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return res.status(500).json(
|
|
226
|
+
createErrorResponse(ERROR_CODES.PROJECT_START_FAILED, error.message)
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
}))
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Stop the Frigg project
|
|
233
|
+
*/
|
|
234
|
+
router.post('/stop', asyncHandler(async (req, res) => {
|
|
235
|
+
if (!projectProcess || projectStatus !== 'running') {
|
|
236
|
+
return res.status(400).json(
|
|
237
|
+
createErrorResponse(ERROR_CODES.PROJECT_NOT_RUNNING, 'Project is not running')
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
projectStatus = 'stopping'
|
|
243
|
+
|
|
244
|
+
const io = req.app.get('io')
|
|
245
|
+
if (io) {
|
|
246
|
+
io.emit('project:status', {
|
|
247
|
+
status: 'stopping',
|
|
248
|
+
message: 'Stopping project...'
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Gracefully terminate the process
|
|
253
|
+
projectProcess.kill('SIGTERM')
|
|
254
|
+
|
|
255
|
+
// Force kill after 5 seconds if still running
|
|
256
|
+
setTimeout(() => {
|
|
257
|
+
if (projectProcess && !projectProcess.killed) {
|
|
258
|
+
projectProcess.kill('SIGKILL')
|
|
259
|
+
}
|
|
260
|
+
}, 5000)
|
|
261
|
+
|
|
262
|
+
res.json(createStandardResponse({
|
|
263
|
+
message: 'Project is stopping',
|
|
264
|
+
status: 'stopping'
|
|
265
|
+
}))
|
|
266
|
+
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return res.status(500).json(
|
|
269
|
+
createErrorResponse(ERROR_CODES.PROJECT_STOP_FAILED, error.message)
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
}))
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Restart the Frigg project
|
|
276
|
+
*/
|
|
277
|
+
router.post('/restart', asyncHandler(async (req, res) => {
|
|
278
|
+
try {
|
|
279
|
+
// Stop if running
|
|
280
|
+
if (projectProcess && projectStatus === 'running') {
|
|
281
|
+
projectProcess.kill('SIGTERM')
|
|
282
|
+
|
|
283
|
+
// Wait for process to exit
|
|
284
|
+
await new Promise((resolve) => {
|
|
285
|
+
if (projectProcess) {
|
|
286
|
+
projectProcess.on('exit', resolve)
|
|
287
|
+
} else {
|
|
288
|
+
resolve()
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Wait a moment
|
|
294
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
295
|
+
|
|
296
|
+
// Start again - we'll simulate calling the start endpoint
|
|
297
|
+
const startResponse = await fetch(`http://localhost:${process.env.PORT || 3001}/api/project/start`, {
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: { 'Content-Type': 'application/json' },
|
|
300
|
+
body: JSON.stringify(req.body)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
const result = await startResponse.json()
|
|
304
|
+
res.json(result)
|
|
305
|
+
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return res.status(500).json(
|
|
308
|
+
createErrorResponse(ERROR_CODES.PROJECT_START_FAILED, error.message)
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
}))
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get project logs
|
|
315
|
+
*/
|
|
316
|
+
router.get('/logs', asyncHandler(async (req, res) => {
|
|
317
|
+
const { limit = 100, type } = req.query
|
|
318
|
+
|
|
319
|
+
let logs = projectLogs
|
|
320
|
+
|
|
321
|
+
if (type && ['stdout', 'stderr'].includes(type)) {
|
|
322
|
+
logs = logs.filter(log => log.type === type)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
res.json(createStandardResponse({
|
|
326
|
+
logs: logs.slice(-parseInt(limit)),
|
|
327
|
+
total: logs.length
|
|
328
|
+
}))
|
|
329
|
+
}))
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Clear project logs
|
|
333
|
+
*/
|
|
334
|
+
router.delete('/logs', asyncHandler(async (req, res) => {
|
|
335
|
+
projectLogs = []
|
|
336
|
+
res.json(createStandardResponse({ message: 'Logs cleared' }))
|
|
337
|
+
}))
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get project metrics
|
|
341
|
+
*/
|
|
342
|
+
router.get('/metrics', asyncHandler(async (req, res) => {
|
|
343
|
+
const metrics = {
|
|
344
|
+
status: projectStatus,
|
|
345
|
+
uptime: projectStartTime ? Math.floor((Date.now() - projectStartTime) / 1000) : 0,
|
|
346
|
+
memory: process.memoryUsage(),
|
|
347
|
+
cpu: process.cpuUsage(),
|
|
348
|
+
logs: {
|
|
349
|
+
total: projectLogs.length,
|
|
350
|
+
errors: projectLogs.filter(log => log.type === 'stderr').length,
|
|
351
|
+
warnings: projectLogs.filter(log => log.message.toLowerCase().includes('warning')).length
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
res.json(createStandardResponse(metrics))
|
|
356
|
+
}))
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get available Frigg repositories
|
|
360
|
+
*/
|
|
361
|
+
router.get('/repositories', asyncHandler(async (req, res) => {
|
|
362
|
+
try {
|
|
363
|
+
let repositories = []
|
|
364
|
+
|
|
365
|
+
// First, check if we have available repositories from the CLI
|
|
366
|
+
if (process.env.AVAILABLE_REPOSITORIES) {
|
|
367
|
+
try {
|
|
368
|
+
repositories = JSON.parse(process.env.AVAILABLE_REPOSITORIES)
|
|
369
|
+
console.log(`Using ${repositories.length} repositories from CLI discovery`)
|
|
370
|
+
} catch (parseError) {
|
|
371
|
+
console.error('Failed to parse AVAILABLE_REPOSITORIES:', parseError)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// If no repositories from CLI, fall back to direct discovery
|
|
376
|
+
if (repositories.length === 0) {
|
|
377
|
+
console.log('No repositories from CLI, executing discovery command...')
|
|
378
|
+
// Execute the frigg CLI command directly
|
|
379
|
+
const friggPath = path.join(__dirname, '../../../frigg-cli/index.js')
|
|
380
|
+
const command = `node "${friggPath}" repos list --json`
|
|
381
|
+
console.log('Executing command:', command)
|
|
382
|
+
console.log('From directory:', process.cwd())
|
|
383
|
+
|
|
384
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
385
|
+
cwd: process.cwd(),
|
|
386
|
+
env: process.env,
|
|
387
|
+
maxBuffer: 1024 * 1024 * 10 // 10MB buffer for large repo lists
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
console.log('Command stdout length:', stdout.length)
|
|
391
|
+
console.log('Command stderr:', stderr)
|
|
392
|
+
|
|
393
|
+
if (stderr && !stderr.includes('DeprecationWarning') && !stderr.includes('NOTE: The AWS SDK')) {
|
|
394
|
+
console.error('Repository discovery stderr:', stderr)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Parse the JSON output
|
|
398
|
+
try {
|
|
399
|
+
// With the --json flag, we should get clean JSON output
|
|
400
|
+
repositories = JSON.parse(stdout)
|
|
401
|
+
console.log(`Found ${repositories.length} repositories via command`)
|
|
402
|
+
} catch (parseError) {
|
|
403
|
+
console.error('Failed to parse repository JSON:', parseError)
|
|
404
|
+
console.log('Raw output (first 500 chars):', stdout.substring(0, 500))
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Get current repository info
|
|
409
|
+
const currentRepo = process.env.REPOSITORY_INFO ?
|
|
410
|
+
JSON.parse(process.env.REPOSITORY_INFO) :
|
|
411
|
+
await getCurrentRepositoryInfo()
|
|
412
|
+
|
|
413
|
+
res.json(createStandardResponse({
|
|
414
|
+
repositories,
|
|
415
|
+
currentRepository: currentRepo,
|
|
416
|
+
isMultiRepo: currentRepo?.isMultiRepo || false
|
|
417
|
+
}))
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error('Failed to get repositories:', error)
|
|
420
|
+
res.json(createStandardResponse({
|
|
421
|
+
repositories: [],
|
|
422
|
+
currentRepository: null,
|
|
423
|
+
isMultiRepo: false,
|
|
424
|
+
error: 'Failed to discover repositories: ' + error.message
|
|
425
|
+
}))
|
|
426
|
+
}
|
|
427
|
+
}))
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Switch to a different repository
|
|
431
|
+
*/
|
|
432
|
+
router.post('/switch-repository', asyncHandler(async (req, res) => {
|
|
433
|
+
const { repositoryPath } = req.body
|
|
434
|
+
|
|
435
|
+
if (!repositoryPath) {
|
|
436
|
+
return res.status(400).json(
|
|
437
|
+
createErrorResponse(ERROR_CODES.VALIDATION_ERROR, 'Repository path is required')
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
// Verify the repository exists and is valid
|
|
443
|
+
const stats = await fs.stat(repositoryPath)
|
|
444
|
+
if (!stats.isDirectory()) {
|
|
445
|
+
throw new Error('Invalid repository path')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Check if it's a valid Frigg repository
|
|
449
|
+
const packageJsonPath = path.join(repositoryPath, 'package.json')
|
|
450
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
|
|
451
|
+
|
|
452
|
+
// Update environment variable
|
|
453
|
+
process.env.PROJECT_ROOT = repositoryPath
|
|
454
|
+
process.env.REPOSITORY_INFO = JSON.stringify({
|
|
455
|
+
name: packageJson.name || path.basename(repositoryPath),
|
|
456
|
+
path: repositoryPath,
|
|
457
|
+
version: packageJson.version
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
// Stop any running processes
|
|
461
|
+
if (projectProcess && projectStatus === 'running') {
|
|
462
|
+
projectProcess.kill('SIGTERM')
|
|
463
|
+
projectStatus = 'stopped'
|
|
464
|
+
projectProcess = null
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Notify via WebSocket
|
|
468
|
+
const io = req.app.get('io')
|
|
469
|
+
if (io) {
|
|
470
|
+
io.emit('repository:switched', {
|
|
471
|
+
repository: {
|
|
472
|
+
name: packageJson.name,
|
|
473
|
+
path: repositoryPath,
|
|
474
|
+
version: packageJson.version
|
|
475
|
+
}
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
res.json(createStandardResponse({
|
|
480
|
+
message: 'Repository switched successfully',
|
|
481
|
+
repository: {
|
|
482
|
+
name: packageJson.name,
|
|
483
|
+
path: repositoryPath,
|
|
484
|
+
version: packageJson.version
|
|
485
|
+
}
|
|
486
|
+
}))
|
|
487
|
+
} catch (error) {
|
|
488
|
+
return res.status(500).json(
|
|
489
|
+
createErrorResponse(ERROR_CODES.INTERNAL_ERROR, 'Failed to switch repository: ' + error.message)
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
}))
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Get current repository information
|
|
496
|
+
*/
|
|
497
|
+
async function getCurrentRepositoryInfo() {
|
|
498
|
+
try {
|
|
499
|
+
const cwd = process.env.PROJECT_ROOT || process.cwd()
|
|
500
|
+
const packageJsonPath = path.join(cwd, 'package.json')
|
|
501
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
name: packageJson.name || path.basename(cwd),
|
|
505
|
+
path: cwd,
|
|
506
|
+
version: packageJson.version,
|
|
507
|
+
framework: detectFramework(packageJson),
|
|
508
|
+
hasBackend: await fs.access(path.join(cwd, 'backend')).then(() => true).catch(() => false)
|
|
509
|
+
}
|
|
510
|
+
} catch (error) {
|
|
511
|
+
return null
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Detect framework from package.json
|
|
517
|
+
*/
|
|
518
|
+
function detectFramework(packageJson) {
|
|
519
|
+
const deps = {
|
|
520
|
+
...packageJson.dependencies,
|
|
521
|
+
...packageJson.devDependencies
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (deps.react) return 'React'
|
|
525
|
+
if (deps.vue) return 'Vue'
|
|
526
|
+
if (deps.svelte) return 'Svelte'
|
|
527
|
+
if (deps['@angular/core']) return 'Angular'
|
|
528
|
+
|
|
529
|
+
return 'Unknown'
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Analyze project integrations
|
|
534
|
+
*/
|
|
535
|
+
router.get('/analyze-integrations', asyncHandler(async (req, res) => {
|
|
536
|
+
try {
|
|
537
|
+
const projectPath = process.env.PROJECT_ROOT || process.cwd()
|
|
538
|
+
// const analysis = await analyzeIntegrations(projectPath)
|
|
539
|
+
const analysis = { integrations: [], patterns: [], recommendations: [] }
|
|
540
|
+
|
|
541
|
+
res.json(createStandardResponse({
|
|
542
|
+
analysis,
|
|
543
|
+
projectPath,
|
|
544
|
+
timestamp: new Date().toISOString()
|
|
545
|
+
}))
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error('Integration analysis failed:', error)
|
|
548
|
+
return res.status(500).json(
|
|
549
|
+
createErrorResponse(ERROR_CODES.INTERNAL_ERROR, 'Failed to analyze integrations: ' + error.message)
|
|
550
|
+
)
|
|
551
|
+
}
|
|
552
|
+
}))
|
|
553
|
+
export default router
|