@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,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@friggframework/management-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"dev:server": "concurrently \"npm run server\" \"npm run dev\"",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"server": "node server/server.js",
|
|
12
|
+
"server:old": "node server/index.js",
|
|
13
|
+
"server:dev": "nodemon server/server.js",
|
|
14
|
+
"test": "vitest",
|
|
15
|
+
"test:ui": "vitest --ui",
|
|
16
|
+
"test:watch": "vitest --watch",
|
|
17
|
+
"test:coverage": "vitest --coverage",
|
|
18
|
+
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
|
19
|
+
"lint:fix": "eslint src --ext js,jsx --fix",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@aws-sdk/client-api-gateway": "^3.478.0",
|
|
24
|
+
"@aws-sdk/client-cloudwatch": "^3.478.0",
|
|
25
|
+
"@aws-sdk/client-lambda": "^3.478.0",
|
|
26
|
+
"@aws-sdk/client-sqs": "^3.478.0",
|
|
27
|
+
"@friggframework/ui": "^2.0.0-next.0",
|
|
28
|
+
"@friggframework/ui-react": "file:../../ui/react",
|
|
29
|
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
30
|
+
"@radix-ui/react-select": "^2.2.5",
|
|
31
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
32
|
+
"axios": "^1.6.7",
|
|
33
|
+
"class-variance-authority": "^0.7.1",
|
|
34
|
+
"clsx": "^2.1.1",
|
|
35
|
+
"cors": "^2.8.5",
|
|
36
|
+
"express": "^4.18.2",
|
|
37
|
+
"framer-motion": "^12.20.1",
|
|
38
|
+
"fs-extra": "^11.2.0",
|
|
39
|
+
"handlebars": "^4.7.8",
|
|
40
|
+
"lucide-react": "^0.473.0",
|
|
41
|
+
"node-cache": "^5.1.2",
|
|
42
|
+
"node-fetch": "^3.3.2",
|
|
43
|
+
"react": "^18.3.1",
|
|
44
|
+
"react-dom": "^18.3.1",
|
|
45
|
+
"react-router-dom": "^6.22.0",
|
|
46
|
+
"semver": "^7.5.4",
|
|
47
|
+
"socket.io": "^4.7.4",
|
|
48
|
+
"socket.io-client": "^4.7.4",
|
|
49
|
+
"tailwind-merge": "^2.6.0",
|
|
50
|
+
"tailwindcss-animate": "^1.0.7"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/react": "^18.3.3",
|
|
54
|
+
"@types/react-dom": "^18.3.0",
|
|
55
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
56
|
+
"autoprefixer": "^10.4.20",
|
|
57
|
+
"concurrently": "^8.2.2",
|
|
58
|
+
"postcss": "^8.4.41",
|
|
59
|
+
"tailwindcss": "^3.4.10",
|
|
60
|
+
"vite": "^5.3.4",
|
|
61
|
+
"eslint": "^8.57.0",
|
|
62
|
+
"eslint-plugin-react": "^7.34.3",
|
|
63
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
64
|
+
"eslint-plugin-react-refresh": "^0.4.7",
|
|
65
|
+
"nodemon": "^3.0.3",
|
|
66
|
+
"typescript": "^5.2.2",
|
|
67
|
+
"vitest": "^1.6.0",
|
|
68
|
+
"@vitest/ui": "^1.6.0",
|
|
69
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
70
|
+
"@testing-library/react": "^14.3.1",
|
|
71
|
+
"@testing-library/jest-dom": "^6.4.6",
|
|
72
|
+
"@testing-library/user-event": "^14.5.2",
|
|
73
|
+
"jsdom": "^24.1.0",
|
|
74
|
+
"msw": "^2.3.1"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { wsHandler } from '../websocket/handler.js';
|
|
6
|
+
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
|
|
9
|
+
// Track backend process
|
|
10
|
+
let backendProcess = null;
|
|
11
|
+
let backendStatus = 'stopped';
|
|
12
|
+
let backendLogs = [];
|
|
13
|
+
const MAX_LOGS = 1000;
|
|
14
|
+
|
|
15
|
+
// Helper function to find the backend directory
|
|
16
|
+
async function findBackendDirectory() {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const possiblePaths = [
|
|
19
|
+
path.join(cwd, 'backend'),
|
|
20
|
+
path.join(cwd, '../../../backend'),
|
|
21
|
+
path.join(cwd, '../../backend'),
|
|
22
|
+
path.join(process.env.HOME || '', 'frigg', 'backend')
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (const backendPath of possiblePaths) {
|
|
26
|
+
if (await fs.pathExists(backendPath)) {
|
|
27
|
+
return backendPath;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
throw new Error('Backend directory not found');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get backend status
|
|
35
|
+
router.get('/status', (req, res) => {
|
|
36
|
+
res.json({
|
|
37
|
+
status: backendStatus,
|
|
38
|
+
pid: backendProcess ? backendProcess.pid : null,
|
|
39
|
+
uptime: backendProcess ? process.uptime() : 0,
|
|
40
|
+
logs: backendLogs.slice(-100) // Return last 100 logs
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Start backend
|
|
45
|
+
router.post('/start', async (req, res) => {
|
|
46
|
+
if (backendProcess && backendStatus === 'running') {
|
|
47
|
+
return res.status(400).json({
|
|
48
|
+
error: 'Backend is already running'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const backendPath = await findBackendDirectory();
|
|
54
|
+
const { stage = 'dev', verbose = false } = req.body;
|
|
55
|
+
|
|
56
|
+
// Clear previous logs
|
|
57
|
+
backendLogs = [];
|
|
58
|
+
backendStatus = 'starting';
|
|
59
|
+
|
|
60
|
+
// Broadcast status update
|
|
61
|
+
wsHandler.broadcast('backend-status', {
|
|
62
|
+
status: 'starting',
|
|
63
|
+
message: 'Starting Frigg backend...'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Start the backend process
|
|
67
|
+
const args = ['run', 'start'];
|
|
68
|
+
if (stage !== 'dev') {
|
|
69
|
+
args.push('--stage', stage);
|
|
70
|
+
}
|
|
71
|
+
if (verbose) {
|
|
72
|
+
args.push('--verbose');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
backendProcess = spawn('npm', args, {
|
|
76
|
+
cwd: backendPath,
|
|
77
|
+
env: { ...process.env, NODE_ENV: stage === 'production' ? 'production' : 'development' },
|
|
78
|
+
shell: true
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Handle stdout
|
|
82
|
+
backendProcess.stdout.on('data', (data) => {
|
|
83
|
+
const log = {
|
|
84
|
+
type: 'stdout',
|
|
85
|
+
message: data.toString(),
|
|
86
|
+
timestamp: new Date().toISOString()
|
|
87
|
+
};
|
|
88
|
+
backendLogs.push(log);
|
|
89
|
+
if (backendLogs.length > MAX_LOGS) {
|
|
90
|
+
backendLogs.shift();
|
|
91
|
+
}
|
|
92
|
+
wsHandler.broadcast('backend-log', log);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Handle stderr
|
|
96
|
+
backendProcess.stderr.on('data', (data) => {
|
|
97
|
+
const log = {
|
|
98
|
+
type: 'stderr',
|
|
99
|
+
message: data.toString(),
|
|
100
|
+
timestamp: new Date().toISOString()
|
|
101
|
+
};
|
|
102
|
+
backendLogs.push(log);
|
|
103
|
+
if (backendLogs.length > MAX_LOGS) {
|
|
104
|
+
backendLogs.shift();
|
|
105
|
+
}
|
|
106
|
+
wsHandler.broadcast('backend-log', log);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Handle process exit
|
|
110
|
+
backendProcess.on('exit', (code, signal) => {
|
|
111
|
+
backendStatus = 'stopped';
|
|
112
|
+
backendProcess = null;
|
|
113
|
+
|
|
114
|
+
const message = {
|
|
115
|
+
status: 'stopped',
|
|
116
|
+
code,
|
|
117
|
+
signal,
|
|
118
|
+
message: `Backend process exited with code ${code}`
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
wsHandler.broadcast('backend-status', message);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Wait a bit to ensure process started
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
126
|
+
|
|
127
|
+
if (backendProcess && !backendProcess.killed) {
|
|
128
|
+
backendStatus = 'running';
|
|
129
|
+
wsHandler.broadcast('backend-status', {
|
|
130
|
+
status: 'running',
|
|
131
|
+
message: 'Backend started successfully'
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
res.json({
|
|
135
|
+
status: 'success',
|
|
136
|
+
message: 'Backend started',
|
|
137
|
+
pid: backendProcess.pid
|
|
138
|
+
});
|
|
139
|
+
} else {
|
|
140
|
+
throw new Error('Failed to start backend process');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
} catch (error) {
|
|
144
|
+
backendStatus = 'stopped';
|
|
145
|
+
res.status(500).json({
|
|
146
|
+
error: error.message,
|
|
147
|
+
details: 'Failed to start backend'
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Stop backend
|
|
153
|
+
router.post('/stop', (req, res) => {
|
|
154
|
+
if (!backendProcess || backendStatus !== 'running') {
|
|
155
|
+
return res.status(400).json({
|
|
156
|
+
error: 'Backend is not running'
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
backendStatus = 'stopping';
|
|
162
|
+
wsHandler.broadcast('backend-status', {
|
|
163
|
+
status: 'stopping',
|
|
164
|
+
message: 'Stopping Frigg backend...'
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Kill the process group
|
|
168
|
+
if (process.platform === 'win32') {
|
|
169
|
+
spawn('taskkill', ['/pid', backendProcess.pid, '/T', '/F']);
|
|
170
|
+
} else {
|
|
171
|
+
process.kill(-backendProcess.pid, 'SIGTERM');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Give it time to shut down gracefully
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
if (backendProcess && !backendProcess.killed) {
|
|
177
|
+
backendProcess.kill('SIGKILL');
|
|
178
|
+
}
|
|
179
|
+
}, 5000);
|
|
180
|
+
|
|
181
|
+
res.json({
|
|
182
|
+
status: 'success',
|
|
183
|
+
message: 'Backend stopping'
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
} catch (error) {
|
|
187
|
+
res.status(500).json({
|
|
188
|
+
error: error.message,
|
|
189
|
+
details: 'Failed to stop backend'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Restart backend
|
|
195
|
+
router.post('/restart', async (req, res) => {
|
|
196
|
+
try {
|
|
197
|
+
// Stop if running
|
|
198
|
+
if (backendProcess && backendStatus === 'running') {
|
|
199
|
+
await new Promise((resolve) => {
|
|
200
|
+
backendProcess.on('exit', resolve);
|
|
201
|
+
|
|
202
|
+
if (process.platform === 'win32') {
|
|
203
|
+
spawn('taskkill', ['/pid', backendProcess.pid, '/T', '/F']);
|
|
204
|
+
} else {
|
|
205
|
+
process.kill(-backendProcess.pid, 'SIGTERM');
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Wait a moment
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
212
|
+
|
|
213
|
+
// Start again
|
|
214
|
+
const response = await fetch('http://localhost:3001/api/backend/start', {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: { 'Content-Type': 'application/json' },
|
|
217
|
+
body: JSON.stringify(req.body)
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const result = await response.json();
|
|
221
|
+
res.json(result);
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
res.status(500).json({
|
|
225
|
+
error: error.message,
|
|
226
|
+
details: 'Failed to restart backend'
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Get logs
|
|
232
|
+
router.get('/logs', (req, res) => {
|
|
233
|
+
const { limit = 100, type } = req.query;
|
|
234
|
+
|
|
235
|
+
let logs = backendLogs;
|
|
236
|
+
|
|
237
|
+
if (type && ['stdout', 'stderr'].includes(type)) {
|
|
238
|
+
logs = logs.filter(log => log.type === type);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
res.json({
|
|
242
|
+
logs: logs.slice(-parseInt(limit)),
|
|
243
|
+
total: logs.length
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Clear logs
|
|
248
|
+
router.delete('/logs', (req, res) => {
|
|
249
|
+
backendLogs = [];
|
|
250
|
+
res.json({
|
|
251
|
+
status: 'success',
|
|
252
|
+
message: 'Logs cleared'
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
export default router;
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import express from 'express'
|
|
2
|
+
import { spawn } from 'child_process'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { createStandardResponse, createErrorResponse, ERROR_CODES, asyncHandler } from '../utils/response.js'
|
|
5
|
+
|
|
6
|
+
const router = express.Router()
|
|
7
|
+
|
|
8
|
+
// Available Frigg CLI commands
|
|
9
|
+
const AVAILABLE_COMMANDS = [
|
|
10
|
+
{
|
|
11
|
+
name: 'init',
|
|
12
|
+
description: 'Initialize a new Frigg project',
|
|
13
|
+
usage: 'frigg init [project-name]',
|
|
14
|
+
options: [
|
|
15
|
+
{ name: '--template', description: 'Template to use (serverless, express)' },
|
|
16
|
+
{ name: '--skip-install', description: 'Skip npm install' },
|
|
17
|
+
{ name: '--force', description: 'Overwrite existing directory' }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'create',
|
|
22
|
+
description: 'Create a new integration module',
|
|
23
|
+
usage: 'frigg create [integration-name]',
|
|
24
|
+
options: [
|
|
25
|
+
{ name: '--template', description: 'Integration template to use' },
|
|
26
|
+
{ name: '--skip-config', description: 'Skip initial configuration' }
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'install',
|
|
31
|
+
description: 'Install an integration module',
|
|
32
|
+
usage: 'frigg install [module-name]',
|
|
33
|
+
options: [
|
|
34
|
+
{ name: '--version', description: 'Specific version to install' },
|
|
35
|
+
{ name: '--save-dev', description: 'Install as dev dependency' }
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'start',
|
|
40
|
+
description: 'Start the Frigg application',
|
|
41
|
+
usage: 'frigg start',
|
|
42
|
+
options: [
|
|
43
|
+
{ name: '--port', description: 'Port to run on' },
|
|
44
|
+
{ name: '--stage', description: 'Environment stage' },
|
|
45
|
+
{ name: '--verbose', description: 'Verbose logging' }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'build',
|
|
50
|
+
description: 'Build the Frigg application',
|
|
51
|
+
usage: 'frigg build',
|
|
52
|
+
options: [
|
|
53
|
+
{ name: '--stage', description: 'Build for specific stage' },
|
|
54
|
+
{ name: '--optimize', description: 'Enable optimizations' }
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'deploy',
|
|
59
|
+
description: 'Deploy the Frigg application',
|
|
60
|
+
usage: 'frigg deploy',
|
|
61
|
+
options: [
|
|
62
|
+
{ name: '--stage', description: 'Deploy to specific stage' },
|
|
63
|
+
{ name: '--region', description: 'AWS region' },
|
|
64
|
+
{ name: '--dry-run', description: 'Preview deployment without executing' }
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'ui',
|
|
69
|
+
description: 'Launch the management UI',
|
|
70
|
+
usage: 'frigg ui',
|
|
71
|
+
options: [
|
|
72
|
+
{ name: '--port', description: 'UI port (default: 3001)' },
|
|
73
|
+
{ name: '--open', description: 'Auto-open browser' }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get available CLI commands
|
|
80
|
+
*/
|
|
81
|
+
router.get('/commands', asyncHandler(async (req, res) => {
|
|
82
|
+
res.json(createStandardResponse({
|
|
83
|
+
commands: AVAILABLE_COMMANDS,
|
|
84
|
+
friggPath: await getFriggPath()
|
|
85
|
+
}))
|
|
86
|
+
}))
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Execute a CLI command
|
|
90
|
+
*/
|
|
91
|
+
router.post('/execute', asyncHandler(async (req, res) => {
|
|
92
|
+
const { command, args = [], options = {} } = req.body
|
|
93
|
+
|
|
94
|
+
if (!command) {
|
|
95
|
+
return res.status(400).json(
|
|
96
|
+
createErrorResponse(ERROR_CODES.INVALID_REQUEST, 'Command is required')
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Validate command
|
|
101
|
+
const validCommand = AVAILABLE_COMMANDS.find(cmd => cmd.name === command)
|
|
102
|
+
if (!validCommand) {
|
|
103
|
+
return res.status(400).json(
|
|
104
|
+
createErrorResponse(ERROR_CODES.CLI_COMMAND_NOT_FOUND, `Unknown command: ${command}`)
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const result = await executeFriggCommand(command, args, options, req.app.get('io'))
|
|
110
|
+
|
|
111
|
+
res.json(createStandardResponse({
|
|
112
|
+
command,
|
|
113
|
+
args,
|
|
114
|
+
options,
|
|
115
|
+
output: result.output,
|
|
116
|
+
exitCode: result.exitCode,
|
|
117
|
+
duration: result.duration
|
|
118
|
+
}))
|
|
119
|
+
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return res.status(500).json(
|
|
122
|
+
createErrorResponse(ERROR_CODES.CLI_COMMAND_FAILED, error.message, {
|
|
123
|
+
command,
|
|
124
|
+
args,
|
|
125
|
+
options
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}))
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get CLI command history
|
|
133
|
+
*/
|
|
134
|
+
router.get('/history', asyncHandler(async (req, res) => {
|
|
135
|
+
// In a real implementation, this would read from a persistent history
|
|
136
|
+
// For now, return empty array
|
|
137
|
+
res.json(createStandardResponse({
|
|
138
|
+
history: [],
|
|
139
|
+
message: 'Command history not yet implemented'
|
|
140
|
+
}))
|
|
141
|
+
}))
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get Frigg CLI version and info
|
|
145
|
+
*/
|
|
146
|
+
router.get('/info', asyncHandler(async (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const result = await executeFriggCommand('--version', [], {}, null)
|
|
149
|
+
|
|
150
|
+
res.json(createStandardResponse({
|
|
151
|
+
version: result.output.trim(),
|
|
152
|
+
path: await getFriggPath(),
|
|
153
|
+
nodeVersion: process.version,
|
|
154
|
+
platform: process.platform
|
|
155
|
+
}))
|
|
156
|
+
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return res.status(500).json(
|
|
159
|
+
createErrorResponse(ERROR_CODES.CLI_COMMAND_FAILED, 'Failed to get CLI info', {
|
|
160
|
+
error: error.message
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}))
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Execute a Frigg CLI command
|
|
168
|
+
*/
|
|
169
|
+
async function executeFriggCommand(command, args = [], options = {}, io = null) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const startTime = Date.now()
|
|
172
|
+
|
|
173
|
+
// Build command arguments
|
|
174
|
+
const cmdArgs = [command, ...args]
|
|
175
|
+
|
|
176
|
+
// Add options as flags
|
|
177
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
178
|
+
if (value === true) {
|
|
179
|
+
cmdArgs.push(`--${key}`)
|
|
180
|
+
} else if (value !== false && value !== null && value !== undefined) {
|
|
181
|
+
cmdArgs.push(`--${key}`, value.toString())
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
let output = ''
|
|
186
|
+
let errorOutput = ''
|
|
187
|
+
|
|
188
|
+
// Try to find frigg command
|
|
189
|
+
const friggCommand = process.platform === 'win32' ? 'frigg.cmd' : 'frigg'
|
|
190
|
+
|
|
191
|
+
// Spawn the command
|
|
192
|
+
const childProcess = spawn(friggCommand, cmdArgs, {
|
|
193
|
+
cwd: process.cwd(),
|
|
194
|
+
env: process.env,
|
|
195
|
+
shell: true
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// Capture stdout
|
|
199
|
+
childProcess.stdout?.on('data', (data) => {
|
|
200
|
+
const chunk = data.toString()
|
|
201
|
+
output += chunk
|
|
202
|
+
|
|
203
|
+
// Broadcast real-time output via WebSocket
|
|
204
|
+
if (io) {
|
|
205
|
+
io.emit('cli:output', {
|
|
206
|
+
type: 'stdout',
|
|
207
|
+
data: chunk,
|
|
208
|
+
command,
|
|
209
|
+
timestamp: new Date().toISOString()
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// Capture stderr
|
|
215
|
+
childProcess.stderr?.on('data', (data) => {
|
|
216
|
+
const chunk = data.toString()
|
|
217
|
+
errorOutput += chunk
|
|
218
|
+
|
|
219
|
+
// Broadcast real-time output via WebSocket
|
|
220
|
+
if (io) {
|
|
221
|
+
io.emit('cli:output', {
|
|
222
|
+
type: 'stderr',
|
|
223
|
+
data: chunk,
|
|
224
|
+
command,
|
|
225
|
+
timestamp: new Date().toISOString()
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Handle process completion
|
|
231
|
+
childProcess.on('close', (code) => {
|
|
232
|
+
const duration = Date.now() - startTime
|
|
233
|
+
|
|
234
|
+
// Broadcast completion via WebSocket
|
|
235
|
+
if (io) {
|
|
236
|
+
io.emit('cli:complete', {
|
|
237
|
+
command,
|
|
238
|
+
args,
|
|
239
|
+
options,
|
|
240
|
+
exitCode: code,
|
|
241
|
+
duration,
|
|
242
|
+
timestamp: new Date().toISOString()
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (code === 0) {
|
|
247
|
+
resolve({
|
|
248
|
+
output: output || errorOutput,
|
|
249
|
+
exitCode: code,
|
|
250
|
+
duration
|
|
251
|
+
})
|
|
252
|
+
} else {
|
|
253
|
+
reject(new Error(`Command failed with exit code ${code}: ${errorOutput || output}`))
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// Handle process errors
|
|
258
|
+
childProcess.on('error', (error) => {
|
|
259
|
+
const duration = Date.now() - startTime
|
|
260
|
+
|
|
261
|
+
// Broadcast error via WebSocket
|
|
262
|
+
if (io) {
|
|
263
|
+
io.emit('cli:error', {
|
|
264
|
+
command,
|
|
265
|
+
args,
|
|
266
|
+
options,
|
|
267
|
+
error: error.message,
|
|
268
|
+
duration,
|
|
269
|
+
timestamp: new Date().toISOString()
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
reject(error)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Set timeout for long-running commands (5 minutes)
|
|
277
|
+
setTimeout(() => {
|
|
278
|
+
if (!childProcess.killed) {
|
|
279
|
+
childProcess.kill('SIGTERM')
|
|
280
|
+
reject(new Error('Command timed out after 5 minutes'))
|
|
281
|
+
}
|
|
282
|
+
}, 5 * 60 * 1000)
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get the path to the Frigg CLI
|
|
288
|
+
*/
|
|
289
|
+
async function getFriggPath() {
|
|
290
|
+
return new Promise((resolve) => {
|
|
291
|
+
const which = process.platform === 'win32' ? 'where' : 'which'
|
|
292
|
+
const friggCommand = process.platform === 'win32' ? 'frigg.cmd' : 'frigg'
|
|
293
|
+
|
|
294
|
+
const childProcess = spawn(which, [friggCommand], { shell: true })
|
|
295
|
+
|
|
296
|
+
let output = ''
|
|
297
|
+
childProcess.stdout?.on('data', (data) => {
|
|
298
|
+
output += data.toString()
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
childProcess.on('close', (code) => {
|
|
302
|
+
if (code === 0) {
|
|
303
|
+
resolve(output.trim().split('\n')[0])
|
|
304
|
+
} else {
|
|
305
|
+
resolve('frigg command not found in PATH')
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
childProcess.on('error', () => {
|
|
310
|
+
resolve('frigg command not found')
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export default router
|