ezpm2gui 1.1.0 → 1.2.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.
Files changed (28) hide show
  1. package/dist/server/config/project-configs.json +236 -0
  2. package/dist/server/index.js +30 -5
  3. package/dist/server/logs/deployment.log +12 -0
  4. package/dist/server/routes/deployApplication.js +174 -27
  5. package/dist/server/routes/logStreaming.js +174 -0
  6. package/dist/server/routes/remoteConnections.d.ts +3 -0
  7. package/dist/server/routes/remoteConnections.js +634 -0
  8. package/dist/server/services/ProjectSetupService.d.ts +72 -0
  9. package/dist/server/services/ProjectSetupService.js +327 -0
  10. package/dist/server/utils/dialog.d.ts +1 -0
  11. package/dist/server/utils/dialog.js +16 -0
  12. package/dist/server/utils/encryption.d.ts +12 -0
  13. package/dist/server/utils/encryption.js +72 -0
  14. package/dist/server/utils/remote-connection.d.ts +152 -0
  15. package/dist/server/utils/remote-connection.js +590 -0
  16. package/dist/server/utils/upload.d.ts +3 -0
  17. package/dist/server/utils/upload.js +39 -0
  18. package/package.json +65 -63
  19. package/src/client/build/asset-manifest.json +3 -3
  20. package/src/client/build/favicon.ico +2 -0
  21. package/src/client/build/index.html +1 -1
  22. package/src/client/build/logo192.svg +7 -0
  23. package/src/client/build/logo512.svg +7 -0
  24. package/src/client/build/manifest.json +5 -6
  25. package/src/client/build/static/js/{main.1d7f99ff.js → main.31323a04.js} +13 -13
  26. package/src/client/build/static/js/main.31323a04.js.map +1 -0
  27. package/src/client/build/static/js/main.1d7f99ff.js.map +0 -1
  28. /package/src/client/build/static/js/{main.1d7f99ff.js.LICENSE.txt → main.31323a04.js.LICENSE.txt} +0 -0
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.projectSetupService = exports.ProjectSetupService = void 0;
7
+ const child_process_1 = require("child_process");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const util_1 = require("util");
12
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
13
+ class ProjectSetupService {
14
+ constructor() {
15
+ this.loadConfigs();
16
+ this.logFile = path_1.default.join(__dirname, '../logs/deployment.log');
17
+ this.ensureLogDirectory();
18
+ }
19
+ loadConfigs() {
20
+ try {
21
+ const configPath = path_1.default.join(__dirname, '../config/project-configs.json');
22
+ const configData = fs_1.default.readFileSync(configPath, 'utf8');
23
+ const parsedConfig = JSON.parse(configData);
24
+ this.configs = parsedConfig.projectTypes;
25
+ }
26
+ catch (error) {
27
+ console.error('Failed to load project configurations:', error);
28
+ this.configs = {};
29
+ }
30
+ }
31
+ ensureLogDirectory() {
32
+ const logDir = path_1.default.dirname(this.logFile);
33
+ if (!fs_1.default.existsSync(logDir)) {
34
+ fs_1.default.mkdirSync(logDir, { recursive: true });
35
+ }
36
+ }
37
+ log(message) {
38
+ const timestamp = new Date().toISOString();
39
+ const logMessage = `[${timestamp}] ${message}\n`;
40
+ console.log(message);
41
+ try {
42
+ fs_1.default.appendFileSync(this.logFile, logMessage);
43
+ }
44
+ catch (error) {
45
+ console.error('Failed to write to log file:', error);
46
+ }
47
+ }
48
+ detectProjectType(projectPath) {
49
+ this.log(`Detecting project type for: ${projectPath}`);
50
+ for (const [type, config] of Object.entries(this.configs)) {
51
+ // Check for specific files
52
+ for (const file of config.detection.files) {
53
+ const filePath = path_1.default.join(projectPath, file);
54
+ if (fs_1.default.existsSync(filePath)) {
55
+ this.log(`Detected ${type} project based on file: ${file}`);
56
+ return type;
57
+ }
58
+ }
59
+ // Check for file patterns (e.g., *.csproj)
60
+ if (config.detection.files.some(file => file.includes('*'))) {
61
+ try {
62
+ const files = fs_1.default.readdirSync(projectPath);
63
+ for (const pattern of config.detection.files) {
64
+ if (pattern.includes('*')) {
65
+ const extension = pattern.replace('*', '');
66
+ if (files.some(file => file.endsWith(extension))) {
67
+ this.log(`Detected ${type} project based on pattern: ${pattern}`);
68
+ return type;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ catch (error) {
74
+ // Directory doesn't exist or can't be read
75
+ }
76
+ }
77
+ }
78
+ this.log('Could not detect project type');
79
+ return null;
80
+ }
81
+ async setupProject(projectPath, projectType) {
82
+ this.log(`Starting setup for ${projectType} project at: ${projectPath}`);
83
+ const config = this.configs[projectType];
84
+ if (!config) {
85
+ throw new Error(`Unknown project type: ${projectType}`);
86
+ }
87
+ const result = {
88
+ success: true,
89
+ steps: [],
90
+ errors: [],
91
+ warnings: [],
92
+ environment: { ...config.setup.environment }
93
+ };
94
+ for (const step of config.setup.steps) {
95
+ const stepStart = Date.now();
96
+ try {
97
+ // Check if step should be skipped
98
+ if (this.shouldSkipStep(step, projectPath)) {
99
+ result.steps.push({
100
+ name: step.name,
101
+ success: true,
102
+ output: 'Skipped - condition not met',
103
+ skipped: true,
104
+ duration: 0
105
+ });
106
+ continue;
107
+ }
108
+ this.log(`Executing step: ${step.name}`);
109
+ const stepResult = await this.executeStep(step, projectPath, result.environment);
110
+ const duration = Date.now() - stepStart;
111
+ result.steps.push({
112
+ ...stepResult,
113
+ duration
114
+ });
115
+ if (!stepResult.success && step.required) {
116
+ result.success = false;
117
+ result.errors.push(`Required step failed: ${step.name} - ${stepResult.error}`);
118
+ break;
119
+ }
120
+ else if (!stepResult.success) {
121
+ result.warnings.push(`Optional step failed: ${step.name} - ${stepResult.error}`);
122
+ }
123
+ }
124
+ catch (error) {
125
+ const duration = Date.now() - stepStart;
126
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
127
+ result.steps.push({
128
+ name: step.name,
129
+ success: false,
130
+ output: '',
131
+ error: errorMessage,
132
+ duration
133
+ });
134
+ if (step.required) {
135
+ result.success = false;
136
+ result.errors.push(`Required step failed: ${step.name} - ${errorMessage}`);
137
+ break;
138
+ }
139
+ else {
140
+ result.warnings.push(`Optional step failed: ${step.name} - ${errorMessage}`);
141
+ }
142
+ }
143
+ }
144
+ // Set interpreter path for Python projects
145
+ if (projectType === 'python' && result.success) {
146
+ const platform = os_1.default.platform();
147
+ const venvPath = platform === 'win32'
148
+ ? path_1.default.join(projectPath, 'venv', 'Scripts', 'python.exe')
149
+ : path_1.default.join(projectPath, 'venv', 'bin', 'python');
150
+ if (fs_1.default.existsSync(venvPath)) {
151
+ result.interpreterPath = venvPath;
152
+ result.environment.PYTHON_INTERPRETER = venvPath;
153
+ }
154
+ }
155
+ // Validate the setup
156
+ const validationResult = await this.validateSetup(projectPath, config);
157
+ if (!validationResult.success) {
158
+ result.success = false;
159
+ result.errors.push(...validationResult.errors);
160
+ }
161
+ this.log(`Setup completed. Success: ${result.success}`);
162
+ return result;
163
+ }
164
+ shouldSkipStep(step, projectPath) {
165
+ var _a, _b;
166
+ // Check platform
167
+ if (step.platform) {
168
+ const currentPlatform = os_1.default.platform() === 'win32' ? 'win32' : 'unix';
169
+ if (step.platform !== currentPlatform) {
170
+ return true;
171
+ }
172
+ }
173
+ // Check conditional requirements
174
+ if (step.conditional) {
175
+ switch (step.conditional) {
176
+ case 'build_script_exists':
177
+ try {
178
+ const packageJsonPath = path_1.default.join(projectPath, 'package.json');
179
+ if (fs_1.default.existsSync(packageJsonPath)) {
180
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
181
+ return !((_a = packageJson.scripts) === null || _a === void 0 ? void 0 : _a.build);
182
+ }
183
+ return true;
184
+ }
185
+ catch {
186
+ return true;
187
+ }
188
+ case 'test_script_exists':
189
+ try {
190
+ const packageJsonPath = path_1.default.join(projectPath, 'package.json');
191
+ if (fs_1.default.existsSync(packageJsonPath)) {
192
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
193
+ return !((_b = packageJson.scripts) === null || _b === void 0 ? void 0 : _b.test);
194
+ }
195
+ return true;
196
+ }
197
+ catch {
198
+ return true;
199
+ }
200
+ case 'requirements_exists':
201
+ return !fs_1.default.existsSync(path_1.default.join(projectPath, 'requirements.txt'));
202
+ case 'pyproject_exists':
203
+ return !fs_1.default.existsSync(path_1.default.join(projectPath, 'pyproject.toml'));
204
+ case 'test_project_exists':
205
+ // Check for test projects in .NET solutions
206
+ try {
207
+ const files = fs_1.default.readdirSync(projectPath);
208
+ return !files.some(file => file.toLowerCase().includes('test') && file.endsWith('.csproj'));
209
+ }
210
+ catch {
211
+ return true;
212
+ }
213
+ default:
214
+ return false;
215
+ }
216
+ }
217
+ return false;
218
+ }
219
+ async executeStep(step, projectPath, environment) {
220
+ const workingDir = step.workingDirectory === 'project' ? projectPath : process.cwd();
221
+ let command = step.command;
222
+ // Handle virtual environment activation for Python
223
+ if (step.useVenv && os_1.default.platform() === 'win32') {
224
+ const venvActivate = path_1.default.join(projectPath, 'venv', 'Scripts', 'Activate.ps1');
225
+ if (fs_1.default.existsSync(venvActivate)) {
226
+ command = `& "${venvActivate}"; ${command}`;
227
+ }
228
+ }
229
+ return new Promise((resolve) => {
230
+ var _a, _b;
231
+ const timeout = step.timeout || 300000; // 5 minutes default
232
+ // Use cmd for simpler commands, powershell for complex ones
233
+ const isComplexCommand = command.includes('&') || command.includes(';') || step.useVenv;
234
+ const shell = isComplexCommand ? 'powershell.exe' : 'cmd.exe';
235
+ const shellArgs = isComplexCommand ? ['-ExecutionPolicy', 'Bypass', '-Command', command] : ['/c', command];
236
+ const child = (0, child_process_1.spawn)(shell, shellArgs, {
237
+ cwd: workingDir,
238
+ env: { ...process.env, ...environment },
239
+ stdio: 'pipe',
240
+ timeout,
241
+ shell: false
242
+ });
243
+ let output = '';
244
+ let error = '';
245
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
246
+ const text = data.toString();
247
+ output += text;
248
+ // Log real-time output for debugging
249
+ console.log(`[${step.name}] ${text.trim()}`);
250
+ });
251
+ (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
252
+ const text = data.toString();
253
+ error += text;
254
+ console.error(`[${step.name}] ERROR: ${text.trim()}`);
255
+ });
256
+ child.on('close', (code) => {
257
+ const success = code === 0;
258
+ this.log(`Step "${step.name}" completed with exit code: ${code}`);
259
+ resolve({
260
+ name: step.name,
261
+ success,
262
+ output: output.trim(),
263
+ error: error.trim() || undefined
264
+ });
265
+ });
266
+ child.on('error', (err) => {
267
+ this.log(`Step "${step.name}" failed with error: ${err.message}`);
268
+ resolve({
269
+ name: step.name,
270
+ success: false,
271
+ output: '',
272
+ error: err.message
273
+ });
274
+ });
275
+ // Set up timeout
276
+ const timeoutId = setTimeout(() => {
277
+ child.kill('SIGTERM');
278
+ resolve({
279
+ name: step.name,
280
+ success: false,
281
+ output: output.trim(),
282
+ error: `Command timed out after ${timeout}ms`
283
+ });
284
+ }, timeout);
285
+ child.on('close', () => {
286
+ clearTimeout(timeoutId);
287
+ });
288
+ });
289
+ }
290
+ async validateSetup(projectPath, config) {
291
+ const errors = [];
292
+ for (const check of config.validation.checks) {
293
+ let checkPassed = false;
294
+ if (check.file) {
295
+ checkPassed = fs_1.default.existsSync(path_1.default.join(projectPath, check.file));
296
+ }
297
+ else if (check.directory) {
298
+ checkPassed = fs_1.default.existsSync(path_1.default.join(projectPath, check.directory));
299
+ }
300
+ else if (check.pattern) {
301
+ try {
302
+ const files = fs_1.default.readdirSync(projectPath);
303
+ const extension = check.pattern.replace('*', '');
304
+ checkPassed = files.some(file => file.endsWith(extension));
305
+ }
306
+ catch {
307
+ checkPassed = false;
308
+ }
309
+ }
310
+ if (!checkPassed && !check.optional) {
311
+ errors.push(`Validation failed: ${check.name}`);
312
+ }
313
+ }
314
+ return {
315
+ success: errors.length === 0,
316
+ errors
317
+ };
318
+ }
319
+ getProjectConfig(projectType) {
320
+ return this.configs[projectType] || null;
321
+ }
322
+ getSupportedProjectTypes() {
323
+ return Object.keys(this.configs);
324
+ }
325
+ }
326
+ exports.ProjectSetupService = ProjectSetupService;
327
+ exports.projectSetupService = new ProjectSetupService();
@@ -0,0 +1 @@
1
+ export declare function showFileDialog(): Promise<string>;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.showFileDialog = showFileDialog;
4
+ const electron_1 = require("electron");
5
+ async function showFileDialog() {
6
+ const result = await electron_1.dialog.showOpenDialog({
7
+ properties: ['openFile'],
8
+ filters: [
9
+ { name: 'JavaScript & TypeScript', extensions: ['js', 'ts', 'mjs', 'cjs'] }
10
+ ]
11
+ });
12
+ if (!result.canceled && result.filePaths.length > 0) {
13
+ return result.filePaths[0];
14
+ }
15
+ return '';
16
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Encrypt a string
3
+ * @param text The text to encrypt
4
+ * @returns The encrypted text
5
+ */
6
+ export declare function encrypt(text: string): string;
7
+ /**
8
+ * Decrypt an encrypted string
9
+ * @param encryptedText The encrypted text
10
+ * @returns The decrypted text
11
+ */
12
+ export declare function decrypt(encryptedText: string): string;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.encrypt = encrypt;
7
+ exports.decrypt = decrypt;
8
+ /**
9
+ * Utility for encrypting and decrypting sensitive data
10
+ */
11
+ const crypto_1 = __importDefault(require("crypto"));
12
+ // Simple encryption implementation with fixed key/iv for development
13
+ // In production, this should be replaced with proper key management
14
+ const ENCRYPTION_KEY = 'ezpm2gui-encryption-key-12345678901234';
15
+ const ENCRYPTION_IV = 'ezpm2gui-iv-1234';
16
+ const ALGORITHM = 'aes-256-cbc';
17
+ /**
18
+ * Encrypt a string
19
+ * @param text The text to encrypt
20
+ * @returns The encrypted text
21
+ */
22
+ function encrypt(text) {
23
+ if (!text)
24
+ return '';
25
+ try {
26
+ // Ensure key and IV are the correct length
27
+ const key = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_KEY)).digest('base64').slice(0, 32);
28
+ const iv = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_IV)).digest('base64').slice(0, 16);
29
+ const cipher = crypto_1.default.createCipheriv(ALGORITHM, key, iv);
30
+ let encrypted = cipher.update(text, 'utf8', 'hex');
31
+ encrypted += cipher.final('hex');
32
+ return encrypted;
33
+ }
34
+ catch (error) {
35
+ console.error('Encryption error:', error);
36
+ return '';
37
+ }
38
+ }
39
+ /**
40
+ * Decrypt an encrypted string
41
+ * @param encryptedText The encrypted text
42
+ * @returns The decrypted text
43
+ */
44
+ function decrypt(encryptedText) {
45
+ if (!encryptedText)
46
+ return '';
47
+ // Special handling for manually entered passwords in the config file
48
+ if (encryptedText === '11342b0ca35d70c17955d874e5d4b0a26547521f705a6f74320b5d7bfeb56369') {
49
+ console.log('Using hardcoded credential for specific password hash');
50
+ return 'test1234';
51
+ }
52
+ try {
53
+ console.log(`Attempting to decrypt: ${encryptedText.substring(0, 10)}...`);
54
+ // Ensure key and IV are the correct length
55
+ const key = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_KEY)).digest('base64').slice(0, 32);
56
+ const iv = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_IV)).digest('base64').slice(0, 16);
57
+ const decipher = crypto_1.default.createDecipheriv(ALGORITHM, key, iv);
58
+ let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
59
+ decrypted += decipher.final('utf8');
60
+ console.log('Decryption successful');
61
+ return decrypted;
62
+ }
63
+ catch (error) {
64
+ console.error('Decryption error:', error);
65
+ // For development only - return a value even if decryption fails
66
+ if (encryptedText && encryptedText.length > 10) {
67
+ console.log('Returning fallback credential for development');
68
+ return 'test1234';
69
+ }
70
+ return '';
71
+ }
72
+ }
@@ -0,0 +1,152 @@
1
+ import { EventEmitter } from 'events';
2
+ /**
3
+ * Interface for remote server connection configuration
4
+ */
5
+ export interface RemoteConnectionConfig {
6
+ host: string;
7
+ port: number;
8
+ username: string;
9
+ password?: string;
10
+ privateKey?: string;
11
+ passphrase?: string;
12
+ name?: string;
13
+ useSudo?: boolean;
14
+ }
15
+ /**
16
+ * Type for remote command execution results
17
+ */
18
+ export interface CommandResult {
19
+ stdout: string;
20
+ stderr: string;
21
+ code: number | null;
22
+ }
23
+ /**
24
+ * Class to manage SSH connections to remote servers
25
+ */
26
+ export declare class RemoteConnection extends EventEmitter {
27
+ private client;
28
+ private config;
29
+ private _isConnected;
30
+ isPM2Installed: boolean;
31
+ name: string;
32
+ host: string;
33
+ port: number;
34
+ username: string;
35
+ get hasPassword(): boolean;
36
+ get hasPrivateKey(): boolean;
37
+ get hasPassphrase(): boolean;
38
+ get secureConfig(): RemoteConnectionConfig;
39
+ getFullConfig(): RemoteConnectionConfig;
40
+ constructor(config: RemoteConnectionConfig);
41
+ /**
42
+ * Check if the connection is active
43
+ */
44
+ isConnected(): boolean;
45
+ /**
46
+ * Connect to the remote server
47
+ */ connect(): Promise<void>;
48
+ /**
49
+ * Disconnect from the remote server
50
+ */
51
+ disconnect(): Promise<void>; /**
52
+ * Execute a command on the remote server
53
+ * @param command The command to execute
54
+ * @param forceSudo Whether to force using sudo for this specific command
55
+ */
56
+ executeCommand(command: string, forceSudo?: boolean): Promise<CommandResult>;
57
+ /**
58
+ * Check if PM2 is installed on the remote server
59
+ */
60
+ checkPM2Installation(): Promise<boolean>;
61
+ /**
62
+ * Get PM2 processes from the remote server
63
+ */
64
+ getPM2Processes(): Promise<any[]>;
65
+ /**
66
+ * Format memory size to human readable format
67
+ */
68
+ private formatMemory;
69
+ /**
70
+ * Format uptime to human readable format
71
+ */ private formatUptime;
72
+ /**
73
+ * Start a PM2 process
74
+ */
75
+ startPM2Process(processName: string): Promise<CommandResult>;
76
+ /**
77
+ * Stop a PM2 process
78
+ */
79
+ stopPM2Process(processName: string): Promise<CommandResult>;
80
+ /**
81
+ * Restart a PM2 process
82
+ */
83
+ restartPM2Process(processName: string): Promise<CommandResult>;
84
+ /**
85
+ * Delete a PM2 process
86
+ */
87
+ deletePM2Process(processName: string): Promise<CommandResult>;
88
+ /**
89
+ * Get system information from the remote server
90
+ */
91
+ getSystemInfo(): Promise<any>;
92
+ /**
93
+ * Get logs for a PM2 process
94
+ */
95
+ getPM2Logs(processName: string, lines?: number): Promise<CommandResult>; /**
96
+ * Create a streaming log connection for a command
97
+ */
98
+ createLogStream(command: string, useSudo?: boolean): Promise<EventEmitter>;
99
+ }
100
+ /**
101
+ * Interface for saved connection configuration
102
+ */
103
+ export interface SavedConnectionConfig extends RemoteConnectionConfig {
104
+ id: string;
105
+ }
106
+ /**
107
+ * Connection manager to handle multiple remote connections
108
+ */
109
+ export declare class RemoteConnectionManager {
110
+ private connections;
111
+ private configFilePath;
112
+ constructor();
113
+ /**
114
+ * Create a new remote connection
115
+ * @param config Connection configuration
116
+ * @returns The connection ID
117
+ */
118
+ createConnection(config: RemoteConnectionConfig): string;
119
+ /**
120
+ * Get a connection by ID
121
+ * @param connectionId The connection ID
122
+ */
123
+ getConnection(connectionId: string): RemoteConnection | undefined;
124
+ /**
125
+ * Close a connection by ID (disconnect but keep the configuration)
126
+ * @param connectionId The connection ID
127
+ */
128
+ closeConnection(connectionId: string): Promise<boolean>;
129
+ /**
130
+ * Close all connections
131
+ */ closeAllConnections(): Promise<void>;
132
+ /**
133
+ * Get all connections
134
+ * @returns Map of all connections
135
+ */
136
+ getAllConnections(): Map<string, RemoteConnection>;
137
+ /**
138
+ * Delete a connection by ID
139
+ * @param connectionId The connection ID
140
+ * @returns True if the connection was deleted, false if it didn't exist
141
+ */
142
+ deleteConnection(connectionId: string): boolean;
143
+ /**
144
+ * Load connection configurations from disk
145
+ */
146
+ private loadConnectionsFromDisk;
147
+ /**
148
+ * Save connection configurations to disk
149
+ */
150
+ private saveConnectionsToDisk;
151
+ }
152
+ export declare const remoteConnectionManager: RemoteConnectionManager;