neex 0.1.3 → 0.1.5

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/dist/src/cli.js CHANGED
@@ -1,20 +1,48 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- // src/cli.ts - Updated version
29
+ // src/cli.ts - Enhanced version with Nodemon and PM2 functionality
7
30
  const commander_1 = require("commander");
8
31
  const index_js_1 = require("./index.js");
32
+ const dev_runner_js_1 = require("./dev-runner.js");
33
+ const process_manager_js_1 = require("./process-manager.js");
9
34
  const chalk_1 = __importDefault(require("chalk"));
10
35
  const figures_1 = __importDefault(require("figures"));
36
+ const path = __importStar(require("path"));
11
37
  const { version } = require('../../package.json');
12
38
  function cli() {
13
39
  const program = new commander_1.Command();
14
40
  let cleanupRunner = null;
41
+ let devRunner = null;
42
+ let processManager = null;
15
43
  program
16
44
  .name('neex')
17
- .description('Professional script runner with beautiful colored output')
45
+ .description('Professional script runner with nodemon and PM2 functionality')
18
46
  .version(version);
19
47
  // Main command for sequential execution (similar to run-s)
20
48
  program
@@ -53,8 +81,8 @@ function cli() {
53
81
  // runx command: parallel execution by default (with alias 'p'), can run sequentially with -q
54
82
  program
55
83
  .command('runx <commands...>', { isDefault: true })
56
- .alias('p')
57
- .description('Run commands in parallel (default) or sequentially with -q. Alias: p')
84
+ .alias('px')
85
+ .description('Run commands in parallel (default) or sequentially with -q. Alias: px')
58
86
  .option('-c, --no-color', 'Disable colored output')
59
87
  .option('-t, --no-timing', 'Hide timing information')
60
88
  .option('-p, --no-prefix', 'Hide command prefix')
@@ -63,6 +91,8 @@ function cli() {
63
91
  .option('-m, --minimal', 'Use minimal output format')
64
92
  .option('-x, --max-parallel <number>', 'Maximum number of parallel processes', parseInt)
65
93
  .option('-q, --sequential', 'Run commands sequentially instead of in parallel')
94
+ .option('--retry <count>', 'Number of times to retry a failed command', parseInt)
95
+ .option('--retry-delay <ms>', 'Delay in milliseconds between retries', parseInt)
66
96
  .action(async (commands, options) => {
67
97
  try {
68
98
  await (0, index_js_1.run)(commands, {
@@ -74,6 +104,8 @@ function cli() {
74
104
  stopOnError: options.stopOnError,
75
105
  printOutput: options.output,
76
106
  minimalOutput: options.minimal,
107
+ retry: options.retry,
108
+ retryDelay: options.retryDelay,
77
109
  registerCleanup: (cleanup) => { cleanupRunner = cleanup; }
78
110
  });
79
111
  }
@@ -87,7 +119,7 @@ function cli() {
87
119
  process.exit(1);
88
120
  }
89
121
  });
90
- // Add a new servers command specifically optimized for running web servers
122
+ // Servers command specifically optimized for running web servers
91
123
  program
92
124
  .command('servers <commands...>')
93
125
  .alias('srv')
@@ -111,7 +143,7 @@ function cli() {
111
143
  printOutput: true,
112
144
  registerCleanup: (cleanup) => { cleanupRunner = cleanup; },
113
145
  groupOutput: options.groupOutput,
114
- isServerMode: true // Special flag for server mode formatting
146
+ isServerMode: true
115
147
  });
116
148
  }
117
149
  catch (error) {
@@ -124,6 +156,438 @@ function cli() {
124
156
  process.exit(1);
125
157
  }
126
158
  });
159
+ // Watch command (Nodemon functionality)
160
+ program
161
+ .command('watch <commands...>')
162
+ .alias('w')
163
+ .description('Run commands with file watching (nodemon functionality)')
164
+ .option('-c, --no-color', 'Disable colored output')
165
+ .option('-t, --no-timing', 'Hide timing information')
166
+ .option('-p, --no-prefix', 'Hide command prefix')
167
+ .option('-s, --stop-on-error', 'Stop on first error')
168
+ .option('-o, --no-output', 'Hide command output')
169
+ .option('-m, --minimal', 'Use minimal output format')
170
+ .option('-w, --watch <paths...>', 'Paths to watch (default: current directory)')
171
+ .option('-i, --ignore <patterns...>', 'Patterns to ignore')
172
+ .option('-e, --ext <extensions...>', 'File extensions to watch (default: js,mjs,json,ts,tsx,jsx)')
173
+ .option('-d, --delay <ms>', 'Delay before restart in milliseconds', parseInt)
174
+ .option('--clear', 'Clear console on restart')
175
+ .option('--verbose', 'Verbose output')
176
+ .option('--signal <signal>', 'Signal to send to processes on restart', 'SIGTERM')
177
+ .action(async (commands, options) => {
178
+ try {
179
+ console.log(chalk_1.default.blue(`${figures_1.default.info} Starting development server with file watching...`));
180
+ const watchPaths = options.watch || ['./'];
181
+ const ignorePatterns = options.ignore || [
182
+ 'node_modules/**',
183
+ '.git/**',
184
+ '*.log',
185
+ 'dist/**',
186
+ 'build/**',
187
+ 'coverage/**',
188
+ '.nyc_output/**',
189
+ '*.tmp',
190
+ '*.temp'
191
+ ];
192
+ const extensions = options.ext || ['js', 'mjs', 'json', 'ts', 'tsx', 'jsx'];
193
+ devRunner = new dev_runner_js_1.DevRunner({
194
+ parallel: false,
195
+ color: options.color,
196
+ showTiming: options.timing,
197
+ prefix: options.prefix,
198
+ stopOnError: options.stopOnError,
199
+ printOutput: options.output,
200
+ minimalOutput: options.minimal,
201
+ watch: watchPaths,
202
+ ignore: ignorePatterns,
203
+ ext: extensions,
204
+ delay: options.delay || 1000,
205
+ clearConsole: options.clear,
206
+ verbose: options.verbose,
207
+ signal: options.signal,
208
+ restartOnChange: true,
209
+ groupOutput: false,
210
+ isServerMode: false // Added missing property
211
+ });
212
+ await devRunner.start(commands);
213
+ }
214
+ catch (error) {
215
+ if (error instanceof Error) {
216
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Watch Error: ${error.message}`));
217
+ }
218
+ else {
219
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown watch error occurred`));
220
+ }
221
+ process.exit(1);
222
+ }
223
+ });
224
+ // Process management commands
225
+ // Start command
226
+ program
227
+ .command('start <script>')
228
+ .description('Start a new process')
229
+ .option('-n, --name <name>', 'Process name')
230
+ .option('-i, --instances <number>', 'Number of instances', parseInt)
231
+ .option('--cwd <path>', 'Working directory')
232
+ .option('--env <env>', 'Environment variables (JSON string)')
233
+ .option('--no-autorestart', 'Disable auto-restart')
234
+ .option('--max-restarts <number>', 'Maximum restarts', parseInt)
235
+ .option('--restart-delay <ms>', 'Restart delay in ms', parseInt)
236
+ .option('--watch', 'Enable file watching')
237
+ .option('--ignore-watch <patterns...>', 'Patterns to ignore when watching')
238
+ .option('--max-memory <size>', 'Max memory before restart (e.g., 1G, 500M)')
239
+ .action(async (script, options) => {
240
+ try {
241
+ if (!processManager) {
242
+ processManager = new process_manager_js_1.ProcessManager();
243
+ }
244
+ const config = {
245
+ id: '',
246
+ name: options.name || path.basename(script, path.extname(script)),
247
+ script,
248
+ cwd: options.cwd,
249
+ env: options.env ? JSON.parse(options.env) : undefined,
250
+ instances: options.instances || 1,
251
+ autorestart: options.autorestart !== false,
252
+ max_restarts: options.maxRestarts || 10,
253
+ restart_delay: options.restartDelay || 1000,
254
+ watch: options.watch || false,
255
+ ignore_watch: options.ignoreWatch,
256
+ max_memory_restart: options.maxMemory
257
+ };
258
+ const id = await processManager.start(config);
259
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Process started with ID: ${id}`));
260
+ }
261
+ catch (error) {
262
+ if (error instanceof Error) {
263
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Start Error: ${error.message}`));
264
+ }
265
+ else {
266
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown start error occurred`));
267
+ }
268
+ process.exit(1);
269
+ }
270
+ });
271
+ // Stop command
272
+ program
273
+ .command('stop <id>')
274
+ .description('Stop a process')
275
+ .action(async (id) => {
276
+ try {
277
+ if (!processManager) {
278
+ processManager = new process_manager_js_1.ProcessManager();
279
+ await processManager.load();
280
+ }
281
+ if (id === 'all') {
282
+ await processManager.stopAll();
283
+ console.log(chalk_1.default.green(`${figures_1.default.tick} All processes stopped`));
284
+ }
285
+ else {
286
+ await processManager.stop(id);
287
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Process ${id} stopped`));
288
+ }
289
+ }
290
+ catch (error) {
291
+ if (error instanceof Error) {
292
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Stop Error: ${error.message}`));
293
+ }
294
+ else {
295
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown stop error occurred`));
296
+ }
297
+ process.exit(1);
298
+ }
299
+ });
300
+ // Restart command
301
+ program
302
+ .command('restart <id>')
303
+ .description('Restart a process')
304
+ .action(async (id) => {
305
+ try {
306
+ if (!processManager) {
307
+ processManager = new process_manager_js_1.ProcessManager();
308
+ await processManager.load();
309
+ }
310
+ if (id === 'all') {
311
+ await processManager.restartAll();
312
+ console.log(chalk_1.default.green(`${figures_1.default.tick} All processes restarted`));
313
+ }
314
+ else {
315
+ await processManager.restart(id);
316
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Process ${id} restarted`));
317
+ }
318
+ }
319
+ catch (error) {
320
+ if (error instanceof Error) {
321
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Restart Error: ${error.message}`));
322
+ }
323
+ else {
324
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown restart error occurred`));
325
+ }
326
+ process.exit(1);
327
+ }
328
+ });
329
+ // Delete command
330
+ program
331
+ .command('delete <id>')
332
+ .description('Delete a process')
333
+ .action(async (id) => {
334
+ try {
335
+ if (!processManager) {
336
+ processManager = new process_manager_js_1.ProcessManager();
337
+ await processManager.load();
338
+ }
339
+ if (id === 'all') {
340
+ await processManager.deleteAll();
341
+ console.log(chalk_1.default.green(`${figures_1.default.tick} All processes deleted`));
342
+ }
343
+ else {
344
+ await processManager.delete(id);
345
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Process ${id} deleted`));
346
+ }
347
+ }
348
+ catch (error) {
349
+ if (error instanceof Error) {
350
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Delete Error: ${error.message}`));
351
+ }
352
+ else {
353
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown delete error occurred`));
354
+ }
355
+ process.exit(1);
356
+ }
357
+ });
358
+ // List/Status command
359
+ program
360
+ .command('list')
361
+ .alias('status')
362
+ .alias('ls')
363
+ .description('List all processes')
364
+ .action(async () => {
365
+ try {
366
+ if (!processManager) {
367
+ processManager = new process_manager_js_1.ProcessManager();
368
+ await processManager.load();
369
+ }
370
+ const processes = await processManager.list();
371
+ if (processes.length === 0) {
372
+ console.log(chalk_1.default.yellow(`${figures_1.default.info} No processes found`));
373
+ return;
374
+ }
375
+ // Print table header
376
+ console.log('\n' + chalk_1.default.bold('Process Management Status'));
377
+ console.log(chalk_1.default.gray('─'.repeat(80)));
378
+ const header = `${chalk_1.default.bold('ID'.padEnd(15))} ${chalk_1.default.bold('Name'.padEnd(20))} ${chalk_1.default.bold('Status'.padEnd(10))} ${chalk_1.default.bold('PID'.padEnd(8))} ${chalk_1.default.bold('Uptime'.padEnd(10))} ${chalk_1.default.bold('Restarts')}`;
379
+ console.log(header);
380
+ console.log(chalk_1.default.gray('─'.repeat(80)));
381
+ // Print process information
382
+ processes.forEach(proc => {
383
+ const statusColor = proc.status === 'online' ? chalk_1.default.green :
384
+ proc.status === 'stopped' ? chalk_1.default.gray :
385
+ proc.status === 'errored' ? chalk_1.default.red : chalk_1.default.yellow;
386
+ const uptime = formatUptime(proc.uptime);
387
+ const pid = proc.pid ? proc.pid.toString() : '-';
388
+ const row = `${proc.id.padEnd(15)} ${proc.name.padEnd(20)} ${statusColor(proc.status.padEnd(10))} ${pid.padEnd(8)} ${uptime.padEnd(10)} ${proc.restarts}`;
389
+ console.log(row);
390
+ });
391
+ console.log(chalk_1.default.gray('─'.repeat(80)));
392
+ console.log(`\n${chalk_1.default.blue(`${figures_1.default.info} Total: ${processes.length} processes`)}`);
393
+ }
394
+ catch (error) {
395
+ if (error instanceof Error) {
396
+ console.error(chalk_1.default.red(`${figures_1.default.cross} List Error: ${error.message}`));
397
+ }
398
+ else {
399
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown list error occurred`));
400
+ }
401
+ process.exit(1);
402
+ }
403
+ });
404
+ // Logs command
405
+ program
406
+ .command('logs [id]')
407
+ .description('Show process logs')
408
+ .option('-f, --follow', 'Follow log output')
409
+ .option('-n, --lines <number>', 'Number of lines to show', parseInt)
410
+ .action(async (id, options) => {
411
+ try {
412
+ if (!processManager) {
413
+ processManager = new process_manager_js_1.ProcessManager();
414
+ await processManager.load();
415
+ }
416
+ if (id) {
417
+ const logs = await processManager.logs(id, options.lines || 100);
418
+ if (logs.length === 0) {
419
+ console.log(chalk_1.default.yellow(`${figures_1.default.info} No logs found for process ${id}`));
420
+ }
421
+ else {
422
+ logs.forEach(log => console.log(log));
423
+ }
424
+ }
425
+ else {
426
+ console.log(chalk_1.default.yellow(`${figures_1.default.info} Please specify a process ID`));
427
+ }
428
+ if (options.follow) {
429
+ console.log(chalk_1.default.blue(`${figures_1.default.info} Following logs... Press Ctrl+C to stop`));
430
+ // TODO: Implement log following
431
+ }
432
+ }
433
+ catch (error) {
434
+ if (error instanceof Error) {
435
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Logs Error: ${error.message}`));
436
+ }
437
+ else {
438
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown logs error occurred`));
439
+ }
440
+ process.exit(1);
441
+ }
442
+ });
443
+ // Save command
444
+ program
445
+ .command('save')
446
+ .description('Save current process list')
447
+ .action(async () => {
448
+ try {
449
+ if (!processManager) {
450
+ processManager = new process_manager_js_1.ProcessManager();
451
+ await processManager.load();
452
+ }
453
+ await processManager.save();
454
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Process list saved`));
455
+ }
456
+ catch (error) {
457
+ if (error instanceof Error) {
458
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Save Error: ${error.message}`));
459
+ }
460
+ else {
461
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown save error occurred`));
462
+ }
463
+ process.exit(1);
464
+ }
465
+ });
466
+ // Startup command (placeholder for system startup configuration)
467
+ program
468
+ .command('startup')
469
+ .description('Generate startup script')
470
+ .action(() => {
471
+ console.log(chalk_1.default.blue(`${figures_1.default.info} Startup script generation:`));
472
+ console.log('\nTo start neex processes on system boot, you can:');
473
+ console.log('1. Create a systemd service (Linux)');
474
+ console.log('2. Use launchd (macOS)');
475
+ console.log('3. Use Windows Service (Windows)');
476
+ console.log('\nExample systemd service file:');
477
+ console.log(chalk_1.default.gray(`
478
+ [Unit]
479
+ Description=Neex Process Manager
480
+ After=network.target
481
+
482
+ [Service]
483
+ Type=simple
484
+ User=your-user
485
+ WorkingDirectory=/path/to/your/project
486
+ ExecStart=/usr/local/bin/neex pm2 resurrect
487
+ Restart=always
488
+
489
+ [Install]
490
+ WantedBy=multi-user.target
491
+ `));
492
+ });
493
+ // Resurrect command (start saved processes)
494
+ program
495
+ .command('resurrect')
496
+ .description('Resurrect previously saved processes')
497
+ .action(async () => {
498
+ try {
499
+ if (!processManager) {
500
+ processManager = new process_manager_js_1.ProcessManager();
501
+ }
502
+ await processManager.load();
503
+ const processes = await processManager.list();
504
+ let started = 0;
505
+ for (const proc of processes) {
506
+ if (proc.status === 'stopped') {
507
+ try {
508
+ await processManager.restart(proc.id);
509
+ started++;
510
+ }
511
+ catch (error) {
512
+ console.log(chalk_1.default.yellow(`${figures_1.default.warning} Failed to start ${proc.id}: ${error.message}`));
513
+ }
514
+ }
515
+ }
516
+ console.log(chalk_1.default.green(`${figures_1.default.tick} Resurrected ${started} processes`));
517
+ }
518
+ catch (error) {
519
+ if (error instanceof Error) {
520
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Resurrect Error: ${error.message}`));
521
+ }
522
+ else {
523
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown resurrect error occurred`));
524
+ }
525
+ process.exit(1);
526
+ }
527
+ });
528
+ // Monit command (monitoring interface)
529
+ program
530
+ .command('monit')
531
+ .description('Launch monitoring interface')
532
+ .action(async () => {
533
+ try {
534
+ if (!processManager) {
535
+ processManager = new process_manager_js_1.ProcessManager();
536
+ await processManager.load();
537
+ }
538
+ console.log(chalk_1.default.blue(`${figures_1.default.info} Starting monitoring interface...`));
539
+ console.log(chalk_1.default.yellow(`${figures_1.default.warning} Press 'q' to quit, 'r' to refresh`));
540
+ // Simple monitoring loop
541
+ const monitorLoop = async () => {
542
+ console.clear();
543
+ console.log(chalk_1.default.bold.blue('Neex Process Monitor'));
544
+ console.log(chalk_1.default.gray('─'.repeat(80)));
545
+ const processes = await processManager.list();
546
+ if (processes.length === 0) {
547
+ console.log(chalk_1.default.yellow(`${figures_1.default.info} No processes running`));
548
+ }
549
+ else {
550
+ processes.forEach(proc => {
551
+ const statusColor = proc.status === 'online' ? chalk_1.default.green :
552
+ proc.status === 'stopped' ? chalk_1.default.gray :
553
+ proc.status === 'errored' ? chalk_1.default.red : chalk_1.default.yellow;
554
+ console.log(`${statusColor('●')} ${proc.name} (${proc.id}) - ${statusColor(proc.status)}`);
555
+ console.log(` PID: ${proc.pid || 'N/A'} | Uptime: ${formatUptime(proc.uptime)} | Restarts: ${proc.restarts}`);
556
+ console.log();
557
+ });
558
+ }
559
+ console.log(chalk_1.default.gray('─'.repeat(80)));
560
+ console.log(chalk_1.default.blue(`Last updated: ${new Date().toLocaleTimeString()}`));
561
+ };
562
+ // Initial display
563
+ await monitorLoop();
564
+ // Set up keyboard input handling
565
+ process.stdin.setRawMode(true);
566
+ process.stdin.resume();
567
+ process.stdin.setEncoding('utf8');
568
+ const interval = setInterval(monitorLoop, 5000);
569
+ process.stdin.on('data', (key) => {
570
+ if (key.toString() === 'q' || key.toString() === '\u0003') { // 'q' or Ctrl+C
571
+ clearInterval(interval);
572
+ process.stdin.setRawMode(false);
573
+ console.log('\n' + chalk_1.default.green(`${figures_1.default.tick} Monitoring stopped`));
574
+ process.exit(0);
575
+ }
576
+ else if (key.toString() === 'r') {
577
+ monitorLoop();
578
+ }
579
+ });
580
+ }
581
+ catch (error) {
582
+ if (error instanceof Error) {
583
+ console.error(chalk_1.default.red(`${figures_1.default.cross} Monit Error: ${error.message}`));
584
+ }
585
+ else {
586
+ console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown monit error occurred`));
587
+ }
588
+ process.exit(1);
589
+ }
590
+ });
127
591
  program.parse(process.argv);
128
592
  // Show help if no commands specified
129
593
  if (program.args.length === 0) {
@@ -132,14 +596,35 @@ function cli() {
132
596
  // Graceful shutdown handling
133
597
  const handleSignal = (signal) => {
134
598
  console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Cleaning up...`)}`);
599
+ if (devRunner && devRunner.isActive()) {
600
+ devRunner.stop();
601
+ }
135
602
  if (cleanupRunner) {
136
603
  cleanupRunner();
137
604
  }
138
- // Give cleanup a moment, then exit
605
+ if (processManager) {
606
+ processManager.dispose();
607
+ }
139
608
  setTimeout(() => process.exit(0), 500);
140
609
  };
141
- process.on('SIGINT', () => handleSignal('SIGINT')); // Ctrl+C
610
+ process.on('SIGINT', () => handleSignal('SIGINT'));
142
611
  process.on('SIGTERM', () => handleSignal('SIGTERM'));
143
612
  process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
144
613
  }
145
614
  exports.default = cli;
615
+ // Helper function to format uptime
616
+ function formatUptime(seconds) {
617
+ if (seconds < 60) {
618
+ return `${seconds}s`;
619
+ }
620
+ else if (seconds < 3600) {
621
+ const minutes = Math.floor(seconds / 60);
622
+ const remainingSeconds = seconds % 60;
623
+ return `${minutes}m ${remainingSeconds}s`;
624
+ }
625
+ else {
626
+ const hours = Math.floor(seconds / 3600);
627
+ const minutes = Math.floor((seconds % 3600) / 60);
628
+ return `${hours}h ${minutes}m`;
629
+ }
630
+ }