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,590 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.remoteConnectionManager = exports.RemoteConnectionManager = exports.RemoteConnection = void 0;
4
+ const ssh2_1 = require("ssh2");
5
+ const events_1 = require("events");
6
+ /**
7
+ * Class to manage SSH connections to remote servers
8
+ */
9
+ class RemoteConnection extends events_1.EventEmitter {
10
+ // Getters for secure access to authentication details
11
+ get hasPassword() {
12
+ return !!this.config.password;
13
+ }
14
+ get hasPrivateKey() {
15
+ return !!this.config.privateKey;
16
+ }
17
+ get hasPassphrase() {
18
+ return !!this.config.passphrase;
19
+ }
20
+ // Getter for secure config (without sensitive data)
21
+ get secureConfig() {
22
+ return {
23
+ name: this.name,
24
+ host: this.host,
25
+ port: this.port,
26
+ username: this.username
27
+ };
28
+ }
29
+ // Method to get the full config for use by connection manager
30
+ getFullConfig() {
31
+ return this.config;
32
+ }
33
+ constructor(config) {
34
+ super();
35
+ this._isConnected = false;
36
+ this.isPM2Installed = false;
37
+ this.client = new ssh2_1.Client();
38
+ this.config = config;
39
+ // Initialize public properties
40
+ this.name = config.name || config.host;
41
+ this.host = config.host;
42
+ this.port = config.port || 22;
43
+ this.username = config.username;
44
+ }
45
+ /**
46
+ * Check if the connection is active
47
+ */
48
+ isConnected() {
49
+ return this._isConnected;
50
+ }
51
+ /**
52
+ * Connect to the remote server
53
+ */ async connect() {
54
+ if (this._isConnected) {
55
+ return Promise.resolve();
56
+ }
57
+ return new Promise((resolve, reject) => {
58
+ this.client.on('ready', () => {
59
+ this._isConnected = true;
60
+ console.log(`Successfully connected to ${this.config.host}`);
61
+ this.emit('connected');
62
+ resolve();
63
+ });
64
+ this.client.on('error', (err) => {
65
+ this._isConnected = false;
66
+ console.error(`SSH connection error for ${this.config.host}:`, err.message);
67
+ reject(err);
68
+ });
69
+ this.client.on('end', () => {
70
+ this._isConnected = false;
71
+ console.log(`Disconnected from ${this.config.host}`);
72
+ this.emit('disconnected');
73
+ });
74
+ // Attempt connection with the provided configuration
75
+ const connectionConfig = {
76
+ host: this.config.host,
77
+ port: this.config.port || 22,
78
+ username: this.config.username,
79
+ // Add reasonable timeouts
80
+ readyTimeout: 10000,
81
+ keepaliveInterval: 30000
82
+ };
83
+ // Debug output
84
+ console.log(`Connecting to ${this.config.host}:${this.config.port} as ${this.config.username}`);
85
+ console.log(`Authentication methods available: ${this.config.password ? 'Password' : 'No password'}, ${this.config.privateKey ? 'Private key' : 'No private key'}`);
86
+ // Add authentication method
87
+ if (this.config.privateKey) {
88
+ connectionConfig.privateKey = this.config.privateKey;
89
+ if (this.config.passphrase) {
90
+ connectionConfig.passphrase = this.config.passphrase;
91
+ }
92
+ }
93
+ else if (this.config.password) {
94
+ connectionConfig.password = this.config.password;
95
+ }
96
+ else {
97
+ console.error('No authentication method provided for', this.config.host);
98
+ return reject(new Error('No authentication method provided'));
99
+ }
100
+ this.client.connect(connectionConfig);
101
+ });
102
+ }
103
+ /**
104
+ * Disconnect from the remote server
105
+ */
106
+ disconnect() {
107
+ return new Promise((resolve) => {
108
+ if (!this._isConnected) {
109
+ resolve();
110
+ return;
111
+ }
112
+ this.client.once('end', () => {
113
+ this._isConnected = false;
114
+ resolve();
115
+ });
116
+ this.client.end();
117
+ });
118
+ } /**
119
+ * Execute a command on the remote server
120
+ * @param command The command to execute
121
+ * @param forceSudo Whether to force using sudo for this specific command
122
+ */
123
+ async executeCommand(command, forceSudo) {
124
+ // Connect if not already connected
125
+ if (!this._isConnected) {
126
+ await this.connect();
127
+ }
128
+ // Apply sudo if configured or explicitly requested for this command
129
+ const useElevatedPrivileges = forceSudo || this.config.useSudo;
130
+ let finalCommand = command;
131
+ // Only apply sudo if it's requested and we have a password
132
+ if (useElevatedPrivileges && this.config.password) {
133
+ finalCommand = `echo '${this.config.password}' | sudo -S ${command}`;
134
+ }
135
+ console.log(`Executing command: ${useElevatedPrivileges ? '[sudo] ' : ''}${command}`);
136
+ return new Promise((resolve, reject) => {
137
+ this.client.exec(finalCommand, (err, channel) => {
138
+ if (err) {
139
+ console.error('Error executing command:', err);
140
+ return reject(err);
141
+ }
142
+ let stdout = '';
143
+ let stderr = '';
144
+ let exitCode = null;
145
+ channel.on('data', (data) => {
146
+ stdout += data.toString();
147
+ });
148
+ channel.stderr.on('data', (data) => {
149
+ stderr += data.toString();
150
+ });
151
+ channel.on('exit', (code) => {
152
+ exitCode = code;
153
+ });
154
+ channel.on('close', () => {
155
+ // Remove sudo password from stdout/stderr if present
156
+ if (useElevatedPrivileges && this.config.password) {
157
+ stdout = stdout.replace(this.config.password, '[PASSWORD REDACTED]');
158
+ stderr = stderr.replace(this.config.password, '[PASSWORD REDACTED]');
159
+ }
160
+ resolve({
161
+ stdout,
162
+ stderr,
163
+ code: exitCode
164
+ });
165
+ });
166
+ channel.on('error', (err) => {
167
+ console.error('Channel error:', err);
168
+ reject(err);
169
+ });
170
+ });
171
+ });
172
+ }
173
+ /**
174
+ * Check if PM2 is installed on the remote server
175
+ */
176
+ async checkPM2Installation() {
177
+ try {
178
+ const result = await this.executeCommand('which pm2 || echo "not installed"');
179
+ this.isPM2Installed = !result.stdout.includes('not installed');
180
+ return this.isPM2Installed;
181
+ }
182
+ catch (error) {
183
+ this.isPM2Installed = false;
184
+ return false;
185
+ }
186
+ }
187
+ /**
188
+ * Get PM2 processes from the remote server
189
+ */
190
+ async getPM2Processes() {
191
+ try {
192
+ // Connect if not already connected
193
+ if (!this._isConnected) {
194
+ await this.connect();
195
+ }
196
+ // Check if PM2 is installed
197
+ if (!this.isPM2Installed) {
198
+ const isPM2Installed = await this.checkPM2Installation();
199
+ if (!isPM2Installed) {
200
+ throw new Error('PM2 is not installed on the remote server');
201
+ }
202
+ }
203
+ const result = await this.executeCommand('pm2 jlist');
204
+ // Clean the output to ensure it's valid JSON
205
+ // Sometimes pm2 jlist can include non-JSON data at the beginning or end
206
+ let cleanedOutput = result.stdout.trim();
207
+ // Find the beginning of the JSON array
208
+ const startIndex = cleanedOutput.indexOf('[');
209
+ // Find the end of the JSON array
210
+ const endIndex = cleanedOutput.lastIndexOf(']') + 1;
211
+ if (startIndex === -1 || endIndex === 0) {
212
+ console.log('Invalid PM2 output format, trying alternative approach');
213
+ // Try using pm2 list --format=json instead
214
+ const listResult = await this.executeCommand('pm2 list --format=json');
215
+ cleanedOutput = listResult.stdout.trim();
216
+ }
217
+ else if (startIndex > 0 || endIndex < cleanedOutput.length) {
218
+ // Extract just the JSON array part
219
+ cleanedOutput = cleanedOutput.substring(startIndex, endIndex);
220
+ }
221
+ try {
222
+ const processList = JSON.parse(cleanedOutput);
223
+ // Format process data similar to local PM2 format
224
+ return processList.map((proc) => ({
225
+ name: proc.name,
226
+ pm_id: proc.pm_id,
227
+ status: proc.pm2_env ? proc.pm2_env.status : 'unknown',
228
+ cpu: proc.monit ? (proc.monit.cpu || 0).toFixed(1) : '0.0',
229
+ memory: proc.monit ? this.formatMemory(proc.monit.memory) : '0 B',
230
+ uptime: proc.pm2_env ? this.formatUptime(proc.pm2_env.pm_uptime) : 'N/A',
231
+ restarts: proc.pm2_env ? (proc.pm2_env.restart_time || 0) : 0
232
+ }));
233
+ }
234
+ catch (error) {
235
+ console.error('Error parsing PM2 process list:', error);
236
+ console.log('Raw output:', result.stdout);
237
+ // Return an empty array instead of throwing
238
+ return [];
239
+ }
240
+ }
241
+ catch (error) {
242
+ console.error('Error getting PM2 processes:', error);
243
+ throw error;
244
+ }
245
+ }
246
+ /**
247
+ * Format memory size to human readable format
248
+ */
249
+ formatMemory(bytes) {
250
+ if (bytes === 0)
251
+ return '0 B';
252
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
253
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
254
+ return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
255
+ }
256
+ /**
257
+ * Format uptime to human readable format
258
+ */ formatUptime(timestamp) {
259
+ if (!timestamp || isNaN(timestamp))
260
+ return 'N/A';
261
+ try {
262
+ const uptime = Date.now() - timestamp;
263
+ if (uptime < 0)
264
+ return 'N/A'; // Invalid timestamp
265
+ const seconds = Math.floor(uptime / 1000);
266
+ if (seconds < 60) {
267
+ return `${seconds}s`;
268
+ }
269
+ else if (seconds < 3600) {
270
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
271
+ }
272
+ else if (seconds < 86400) {
273
+ const hours = Math.floor(seconds / 3600);
274
+ const mins = Math.floor((seconds % 3600) / 60);
275
+ return `${hours}h ${mins}m`;
276
+ }
277
+ else {
278
+ const days = Math.floor(seconds / 86400);
279
+ const hours = Math.floor((seconds % 86400) / 3600);
280
+ return `${days}d ${hours}h`;
281
+ }
282
+ }
283
+ catch (error) {
284
+ console.error('Error formatting uptime:', error);
285
+ return 'N/A';
286
+ }
287
+ }
288
+ /**
289
+ * Start a PM2 process
290
+ */
291
+ async startPM2Process(processName) {
292
+ return await this.executeCommand(`pm2 start ${processName}`);
293
+ }
294
+ /**
295
+ * Stop a PM2 process
296
+ */
297
+ async stopPM2Process(processName) {
298
+ return await this.executeCommand(`pm2 stop ${processName}`);
299
+ }
300
+ /**
301
+ * Restart a PM2 process
302
+ */
303
+ async restartPM2Process(processName) {
304
+ return await this.executeCommand(`pm2 restart ${processName}`);
305
+ }
306
+ /**
307
+ * Delete a PM2 process
308
+ */
309
+ async deletePM2Process(processName) {
310
+ return await this.executeCommand(`pm2 delete ${processName}`);
311
+ }
312
+ /**
313
+ * Get system information from the remote server
314
+ */
315
+ async getSystemInfo() {
316
+ try {
317
+ // Connect if not already connected
318
+ if (!this._isConnected) {
319
+ await this.connect();
320
+ }
321
+ // Run a series of commands to get system information
322
+ const [hostname, platform, arch, nodeVersion, memInfo, cpuInfo] = await Promise.all([
323
+ this.executeCommand('hostname'),
324
+ this.executeCommand('uname -s'),
325
+ this.executeCommand('uname -m'),
326
+ this.executeCommand('node -v'),
327
+ this.executeCommand('free -b'),
328
+ this.executeCommand('cat /proc/cpuinfo | grep processor | wc -l')
329
+ ]);
330
+ // Parse memory information
331
+ const memLines = memInfo.stdout.split('\n');
332
+ let totalMemory = 0;
333
+ let freeMemory = 0;
334
+ if (memLines.length > 1) {
335
+ const memValues = memLines[1].split(/\s+/);
336
+ if (memValues.length > 6) {
337
+ totalMemory = parseInt(memValues[1], 10);
338
+ freeMemory = parseInt(memValues[3], 10);
339
+ }
340
+ }
341
+ // Get load average
342
+ const loadAvgResult = await this.executeCommand('cat /proc/loadavg');
343
+ const loadAvg = loadAvgResult.stdout.trim().split(' ').slice(0, 3).map(parseFloat);
344
+ return {
345
+ hostname: hostname.stdout.trim(),
346
+ platform: platform.stdout.trim(),
347
+ arch: arch.stdout.trim(),
348
+ nodeVersion: nodeVersion.stdout.trim(),
349
+ totalMemory: this.formatMemory(totalMemory),
350
+ freeMemory: this.formatMemory(freeMemory),
351
+ cpuCount: parseInt(cpuInfo.stdout.trim(), 10),
352
+ loadAverage: loadAvg
353
+ };
354
+ }
355
+ catch (error) {
356
+ console.error('Error getting system info:', error);
357
+ throw error;
358
+ }
359
+ }
360
+ /**
361
+ * Get logs for a PM2 process
362
+ */
363
+ async getPM2Logs(processName, lines = 100) {
364
+ return await this.executeCommand(`pm2 logs ${processName} --lines ${lines} --nostream`);
365
+ } /**
366
+ * Create a streaming log connection for a command
367
+ */
368
+ async createLogStream(command, useSudo = false) {
369
+ if (!this._isConnected) {
370
+ throw new Error('Connection not established');
371
+ }
372
+ let finalCommand = command;
373
+ let isInitialized = false;
374
+ // Apply sudo if requested and we have a password
375
+ if (useSudo && this.config.useSudo && this.config.password) {
376
+ // Use echo to pipe the password to sudo for streaming commands
377
+ finalCommand = `echo '${this.config.password}' | sudo -S ${command}`;
378
+ }
379
+ console.log(`[createLogStream] Executing command: ${finalCommand.replace(this.config.password || '', '[PASSWORD]')}`);
380
+ return new Promise((resolve, reject) => {
381
+ this.client.exec(finalCommand, (err, stream) => {
382
+ var _a;
383
+ if (err) {
384
+ console.error(`[createLogStream] Exec error:`, err);
385
+ reject(err);
386
+ return;
387
+ }
388
+ const logEmitter = new events_1.EventEmitter();
389
+ stream.on('data', (data) => {
390
+ const dataStr = data.toString();
391
+ console.log(`[createLogStream] Raw data:`, dataStr);
392
+ // Skip the sudo password prompt and initial setup messages
393
+ if (!isInitialized) {
394
+ if (dataStr.includes('[sudo] password') ||
395
+ dataStr.includes('Password:') ||
396
+ dataStr.trim() === '') {
397
+ return; // Skip initialization messages
398
+ }
399
+ isInitialized = true;
400
+ }
401
+ logEmitter.emit('data', dataStr);
402
+ });
403
+ (_a = stream.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
404
+ const dataStr = data.toString();
405
+ console.log(`[createLogStream] Stderr data:`, dataStr);
406
+ // Don't emit stderr data for sudo prompts or permission messages
407
+ if (!dataStr.includes('[sudo] password') &&
408
+ !dataStr.includes('Password:') &&
409
+ !dataStr.includes('Sorry, try again')) {
410
+ logEmitter.emit('data', dataStr);
411
+ }
412
+ });
413
+ stream.on('close', (code) => {
414
+ console.log(`[createLogStream] Stream closed with code:`, code);
415
+ logEmitter.emit('close', code);
416
+ });
417
+ stream.on('error', (error) => {
418
+ console.error(`[createLogStream] Stream error:`, error);
419
+ logEmitter.emit('error', error);
420
+ });
421
+ // Add a method to kill the stream
422
+ logEmitter.kill = () => {
423
+ console.log(`[createLogStream] Killing stream`);
424
+ stream.close();
425
+ };
426
+ resolve(logEmitter);
427
+ });
428
+ });
429
+ }
430
+ }
431
+ exports.RemoteConnection = RemoteConnection;
432
+ /**
433
+ * Connection manager to handle multiple remote connections
434
+ */
435
+ class RemoteConnectionManager {
436
+ constructor() {
437
+ this.connections = new Map();
438
+ this.configFilePath = 'D:/Personal/ezpm2gui/src/server/config/remote-connections.json';
439
+ this.loadConnectionsFromDisk();
440
+ }
441
+ /**
442
+ * Create a new remote connection
443
+ * @param config Connection configuration
444
+ * @returns The connection ID
445
+ */
446
+ createConnection(config) {
447
+ const connectionId = `${config.host}-${config.port}-${config.username}`;
448
+ if (this.connections.has(connectionId)) {
449
+ return connectionId;
450
+ }
451
+ const connection = new RemoteConnection(config);
452
+ this.connections.set(connectionId, connection);
453
+ // Save the updated connections to disk
454
+ this.saveConnectionsToDisk();
455
+ return connectionId;
456
+ }
457
+ /**
458
+ * Get a connection by ID
459
+ * @param connectionId The connection ID
460
+ */
461
+ getConnection(connectionId) {
462
+ return this.connections.get(connectionId);
463
+ }
464
+ /**
465
+ * Close a connection by ID (disconnect but keep the configuration)
466
+ * @param connectionId The connection ID
467
+ */
468
+ async closeConnection(connectionId) {
469
+ const connection = this.connections.get(connectionId);
470
+ if (connection) {
471
+ await connection.disconnect();
472
+ return true;
473
+ }
474
+ return false;
475
+ }
476
+ /**
477
+ * Close all connections
478
+ */ async closeAllConnections() {
479
+ const closePromises = Array.from(this.connections.values()).map((conn) => conn.disconnect());
480
+ await Promise.all(closePromises);
481
+ this.connections.clear();
482
+ }
483
+ /**
484
+ * Get all connections
485
+ * @returns Map of all connections
486
+ */
487
+ getAllConnections() {
488
+ return this.connections;
489
+ }
490
+ /**
491
+ * Delete a connection by ID
492
+ * @param connectionId The connection ID
493
+ * @returns True if the connection was deleted, false if it didn't exist
494
+ */
495
+ deleteConnection(connectionId) {
496
+ const deleted = this.connections.delete(connectionId);
497
+ if (deleted) {
498
+ // Save the updated connections to disk
499
+ this.saveConnectionsToDisk();
500
+ }
501
+ return deleted;
502
+ }
503
+ /**
504
+ * Load connection configurations from disk
505
+ */
506
+ loadConnectionsFromDisk() {
507
+ try {
508
+ const fs = require('fs');
509
+ const path = require('path');
510
+ const { decrypt } = require('./encryption');
511
+ // Ensure the config directory exists
512
+ const configDir = path.dirname(this.configFilePath);
513
+ if (!fs.existsSync(configDir)) {
514
+ fs.mkdirSync(configDir, { recursive: true });
515
+ }
516
+ // Create the file with default content if it doesn't exist
517
+ if (!fs.existsSync(this.configFilePath)) {
518
+ fs.writeFileSync(this.configFilePath, JSON.stringify({ connections: [] }, null, 2));
519
+ }
520
+ // Read the configuration file
521
+ const data = fs.readFileSync(this.configFilePath, 'utf8');
522
+ const config = JSON.parse(data);
523
+ if (config && Array.isArray(config.connections)) {
524
+ // Create connections from saved configs
525
+ config.connections.forEach((conn) => {
526
+ const connectionConfig = {
527
+ name: conn.name,
528
+ host: conn.host,
529
+ port: conn.port,
530
+ username: conn.username,
531
+ // Decrypt sensitive data if it exists
532
+ password: conn.password ? decrypt(conn.password) : undefined,
533
+ privateKey: conn.privateKey ? decrypt(conn.privateKey) : undefined,
534
+ passphrase: conn.passphrase ? decrypt(conn.passphrase) : undefined,
535
+ useSudo: conn.useSudo
536
+ };
537
+ // Re-create the connection with the original ID
538
+ const connection = new RemoteConnection(connectionConfig);
539
+ this.connections.set(conn.id, connection);
540
+ });
541
+ console.log(`Loaded ${config.connections.length} remote connections from disk`);
542
+ }
543
+ }
544
+ catch (error) {
545
+ console.error('Error loading remote connections from disk:', error);
546
+ }
547
+ }
548
+ /**
549
+ * Save connection configurations to disk
550
+ */
551
+ saveConnectionsToDisk() {
552
+ try {
553
+ const fs = require('fs');
554
+ const path = require('path');
555
+ const { encrypt } = require('./encryption');
556
+ // Ensure the config directory exists
557
+ const configDir = path.dirname(this.configFilePath);
558
+ if (!fs.existsSync(configDir)) {
559
+ fs.mkdirSync(configDir, { recursive: true });
560
+ }
561
+ // Convert connections map to serializable array with encrypted sensitive data
562
+ const savedConnections = Array.from(this.connections.entries()).map(([id, conn]) => {
563
+ // Get the original configuration to access sensitive data
564
+ const config = conn.getFullConfig();
565
+ return {
566
+ id,
567
+ name: conn.name,
568
+ host: conn.host,
569
+ port: conn.port,
570
+ username: conn.username,
571
+ password: config.password ? encrypt(config.password) : undefined,
572
+ privateKey: config.privateKey ? encrypt(config.privateKey) : undefined,
573
+ passphrase: config.passphrase ? encrypt(config.passphrase) : undefined,
574
+ useSudo: config.useSudo
575
+ };
576
+ });
577
+ // Write to file
578
+ fs.writeFileSync(this.configFilePath, JSON.stringify({
579
+ connections: savedConnections
580
+ }, null, 2));
581
+ console.log(`Saved ${savedConnections.length} remote connections to disk`);
582
+ }
583
+ catch (error) {
584
+ console.error('Error saving remote connections to disk:', error);
585
+ }
586
+ }
587
+ }
588
+ exports.RemoteConnectionManager = RemoteConnectionManager;
589
+ // Create a singleton instance of the connection manager
590
+ exports.remoteConnectionManager = new RemoteConnectionManager();
@@ -0,0 +1,3 @@
1
+ import multer from 'multer';
2
+ declare const upload: multer.Multer;
3
+ export default upload;
@@ -0,0 +1,39 @@
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
+ const multer_1 = __importDefault(require("multer"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ // Create uploads directory if it doesn't exist
10
+ const uploadsDir = path_1.default.join(process.cwd(), 'uploads');
11
+ if (!fs_1.default.existsSync(uploadsDir)) {
12
+ fs_1.default.mkdirSync(uploadsDir, { recursive: true });
13
+ }
14
+ // Configure multer for file upload
15
+ const storage = multer_1.default.diskStorage({
16
+ destination: function (req, file, cb) {
17
+ cb(null, uploadsDir);
18
+ },
19
+ filename: function (req, file, cb) {
20
+ // Use original filename to preserve extension
21
+ cb(null, file.originalname);
22
+ }
23
+ });
24
+ // Create the multer instance
25
+ const upload = (0, multer_1.default)({
26
+ storage: storage,
27
+ fileFilter: (req, file, cb) => {
28
+ // Allow only JavaScript and TypeScript files
29
+ const allowedExtensions = ['.js', '.ts', '.mjs', '.cjs'];
30
+ const ext = path_1.default.extname(file.originalname).toLowerCase();
31
+ if (allowedExtensions.includes(ext)) {
32
+ cb(null, true);
33
+ }
34
+ else {
35
+ cb(new Error('Only JavaScript and TypeScript files are allowed'));
36
+ }
37
+ }
38
+ });
39
+ exports.default = upload;