@git.zone/cli 1.21.5 → 2.1.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 (61) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/mod_commit/index.js +5 -2
  3. package/dist_ts/mod_commit/mod.helpers.js +24 -9
  4. package/dist_ts/mod_format/classes.baseformatter.js +4 -4
  5. package/dist_ts/mod_format/classes.changecache.js +29 -17
  6. package/dist_ts/mod_format/classes.diffreporter.js +10 -4
  7. package/dist_ts/mod_format/classes.formatstats.js +5 -2
  8. package/dist_ts/mod_format/classes.rollbackmanager.js +41 -17
  9. package/dist_ts/mod_format/format.cleanup.js +5 -3
  10. package/dist_ts/mod_format/format.copy.js +12 -4
  11. package/dist_ts/mod_format/format.gitignore.js +14 -5
  12. package/dist_ts/mod_format/format.license.js +4 -2
  13. package/dist_ts/mod_format/format.packagejson.js +6 -2
  14. package/dist_ts/mod_format/format.readme.js +9 -5
  15. package/dist_ts/mod_format/format.tsconfig.js +7 -4
  16. package/dist_ts/mod_format/formatters/cleanup.formatter.js +2 -2
  17. package/dist_ts/mod_format/formatters/prettier.formatter.js +19 -6
  18. package/dist_ts/mod_format/index.js +9 -3
  19. package/dist_ts/mod_meta/meta.classes.meta.js +82 -17
  20. package/dist_ts/mod_services/classes.globalregistry.d.ts +77 -0
  21. package/dist_ts/mod_services/classes.globalregistry.js +133 -0
  22. package/dist_ts/mod_services/classes.serviceconfiguration.d.ts +7 -0
  23. package/dist_ts/mod_services/classes.serviceconfiguration.js +86 -10
  24. package/dist_ts/mod_services/classes.servicemanager.d.ts +38 -0
  25. package/dist_ts/mod_services/classes.servicemanager.js +353 -10
  26. package/dist_ts/mod_services/helpers.d.ts +1 -1
  27. package/dist_ts/mod_services/helpers.js +8 -4
  28. package/dist_ts/mod_services/index.js +194 -28
  29. package/dist_ts/mod_standard/index.js +22 -9
  30. package/dist_ts/mod_template/index.js +2 -2
  31. package/dist_ts/plugins.d.ts +2 -0
  32. package/dist_ts/plugins.js +4 -1
  33. package/package.json +7 -6
  34. package/readme.hints.md +55 -2
  35. package/ts/00_commitinfo_data.ts +1 -1
  36. package/ts/mod_commit/index.ts +4 -4
  37. package/ts/mod_commit/mod.helpers.ts +23 -11
  38. package/ts/mod_format/classes.baseformatter.ts +3 -3
  39. package/ts/mod_format/classes.changecache.ts +28 -16
  40. package/ts/mod_format/classes.diffreporter.ts +9 -8
  41. package/ts/mod_format/classes.formatstats.ts +4 -4
  42. package/ts/mod_format/classes.rollbackmanager.ts +40 -18
  43. package/ts/mod_format/format.cleanup.ts +4 -4
  44. package/ts/mod_format/format.copy.ts +11 -3
  45. package/ts/mod_format/format.gitignore.ts +13 -6
  46. package/ts/mod_format/format.license.ts +3 -3
  47. package/ts/mod_format/format.packagejson.ts +5 -3
  48. package/ts/mod_format/format.readme.ts +8 -11
  49. package/ts/mod_format/format.tsconfig.ts +6 -5
  50. package/ts/mod_format/formatters/cleanup.formatter.ts +1 -1
  51. package/ts/mod_format/formatters/prettier.formatter.ts +18 -8
  52. package/ts/mod_format/index.ts +10 -5
  53. package/ts/mod_meta/meta.classes.meta.ts +84 -41
  54. package/ts/mod_services/classes.globalregistry.ts +190 -0
  55. package/ts/mod_services/classes.serviceconfiguration.ts +121 -35
  56. package/ts/mod_services/classes.servicemanager.ts +405 -32
  57. package/ts/mod_services/helpers.ts +8 -4
  58. package/ts/mod_services/index.ts +257 -48
  59. package/ts/mod_standard/index.ts +23 -10
  60. package/ts/mod_template/index.ts +1 -1
  61. package/ts/plugins.ts +4 -0
@@ -2,17 +2,21 @@ import * as plugins from './mod.plugins.js';
2
2
  import * as helpers from './helpers.js';
3
3
  import { ServiceConfiguration } from './classes.serviceconfiguration.js';
4
4
  import { DockerContainer } from './classes.dockercontainer.js';
5
+ import { GlobalRegistry } from './classes.globalregistry.js';
5
6
  import { logger } from '../gitzone.logging.js';
6
7
 
7
8
  export class ServiceManager {
8
9
  private config: ServiceConfiguration;
9
10
  private docker: DockerContainer;
10
-
11
+ private enabledServices: string[] | null = null;
12
+ private globalRegistry: GlobalRegistry;
13
+
11
14
  constructor() {
12
15
  this.config = new ServiceConfiguration();
13
16
  this.docker = new DockerContainer();
17
+ this.globalRegistry = GlobalRegistry.getInstance();
14
18
  }
15
-
19
+
16
20
  /**
17
21
  * Initialize the service manager
18
22
  */
@@ -22,15 +26,162 @@ export class ServiceManager {
22
26
  logger.log('error', 'Error: Docker is not installed. Please install Docker first.');
23
27
  process.exit(1);
24
28
  }
25
-
29
+
26
30
  // Load or create configuration
27
31
  await this.config.loadOrCreate();
28
32
  logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`);
29
-
33
+
34
+ // Load service selection from npmextra.json
35
+ await this.loadServiceConfiguration();
36
+
30
37
  // Validate and update ports if needed
31
38
  await this.config.validateAndUpdatePorts();
32
39
  }
33
-
40
+
41
+ /**
42
+ * Load service configuration from npmextra.json
43
+ */
44
+ private async loadServiceConfiguration(): Promise<void> {
45
+ const npmextraConfig = new plugins.npmextra.Npmextra(process.cwd());
46
+ const gitzoneConfig = npmextraConfig.dataFor<any>('gitzone', {});
47
+
48
+ // Check if services array exists
49
+ if (!gitzoneConfig.services || !Array.isArray(gitzoneConfig.services) || gitzoneConfig.services.length === 0) {
50
+ // Prompt user to select services
51
+ const smartinteract = new plugins.smartinteract.SmartInteract();
52
+ const response = await smartinteract.askQuestion({
53
+ name: 'services',
54
+ type: 'checkbox',
55
+ message: 'Which services do you want to enable for this project?',
56
+ choices: [
57
+ { name: 'MongoDB', value: 'mongodb' },
58
+ { name: 'MinIO (S3)', value: 'minio' },
59
+ { name: 'Elasticsearch', value: 'elasticsearch' }
60
+ ],
61
+ default: ['mongodb', 'minio', 'elasticsearch']
62
+ });
63
+
64
+ this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
65
+
66
+ // Save to npmextra.json
67
+ await this.saveServiceConfiguration(this.enabledServices);
68
+ } else {
69
+ this.enabledServices = gitzoneConfig.services;
70
+ logger.log('info', `🔧 Enabled services: ${this.enabledServices.join(', ')}`);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Save service configuration to npmextra.json
76
+ */
77
+ private async saveServiceConfiguration(services: string[]): Promise<void> {
78
+ const npmextraPath = plugins.path.join(process.cwd(), 'npmextra.json');
79
+ let npmextraData: any = {};
80
+
81
+ // Read existing npmextra.json if it exists
82
+ if (await plugins.smartfs.file(npmextraPath).exists()) {
83
+ const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
84
+ npmextraData = JSON.parse(content as string);
85
+ }
86
+
87
+ // Update gitzone.services
88
+ if (!npmextraData.gitzone) {
89
+ npmextraData.gitzone = {};
90
+ }
91
+ npmextraData.gitzone.services = services;
92
+
93
+ // Write back to npmextra.json
94
+ await plugins.smartfs
95
+ .file(npmextraPath)
96
+ .encoding('utf8')
97
+ .write(JSON.stringify(npmextraData, null, 2));
98
+
99
+ logger.log('ok', `✅ Saved service configuration to npmextra.json`);
100
+ logger.log('info', `🔧 Enabled services: ${services.join(', ')}`);
101
+ }
102
+
103
+ /**
104
+ * Check if a service is enabled
105
+ */
106
+ private isServiceEnabled(service: string): boolean {
107
+ if (!this.enabledServices) {
108
+ return true; // If no configuration, enable all
109
+ }
110
+ return this.enabledServices.includes(service);
111
+ }
112
+
113
+ /**
114
+ * Register this project with the global registry
115
+ */
116
+ private async registerWithGlobalRegistry(): Promise<void> {
117
+ const config = this.config.getConfig();
118
+ const containers = this.config.getContainerNames();
119
+
120
+ await this.globalRegistry.registerProject({
121
+ projectPath: process.cwd(),
122
+ projectName: config.PROJECT_NAME,
123
+ containers: {
124
+ mongo: containers.mongo,
125
+ minio: containers.minio,
126
+ elasticsearch: containers.elasticsearch,
127
+ },
128
+ ports: {
129
+ mongo: parseInt(config.MONGODB_PORT),
130
+ s3: parseInt(config.S3_PORT),
131
+ s3Console: parseInt(config.S3_CONSOLE_PORT),
132
+ elasticsearch: parseInt(config.ELASTICSEARCH_PORT),
133
+ },
134
+ enabledServices: this.enabledServices || ['mongodb', 'minio', 'elasticsearch'],
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Start all enabled services
140
+ */
141
+ public async startAll(): Promise<void> {
142
+ let first = true;
143
+ if (this.isServiceEnabled('mongodb')) {
144
+ if (!first) console.log();
145
+ await this.startMongoDB();
146
+ first = false;
147
+ }
148
+ if (this.isServiceEnabled('minio')) {
149
+ if (!first) console.log();
150
+ await this.startMinIO();
151
+ first = false;
152
+ }
153
+ if (this.isServiceEnabled('elasticsearch')) {
154
+ if (!first) console.log();
155
+ await this.startElasticsearch();
156
+ first = false;
157
+ }
158
+
159
+ // Register with global registry
160
+ await this.registerWithGlobalRegistry();
161
+ }
162
+
163
+ /**
164
+ * Stop all enabled services
165
+ */
166
+ public async stopAll(): Promise<void> {
167
+ let first = true;
168
+ if (this.isServiceEnabled('mongodb')) {
169
+ if (!first) console.log();
170
+ await this.stopMongoDB();
171
+ first = false;
172
+ }
173
+ if (this.isServiceEnabled('minio')) {
174
+ if (!first) console.log();
175
+ await this.stopMinIO();
176
+ first = false;
177
+ }
178
+ if (this.isServiceEnabled('elasticsearch')) {
179
+ if (!first) console.log();
180
+ await this.stopElasticsearch();
181
+ first = false;
182
+ }
183
+ }
184
+
34
185
  /**
35
186
  * Start MongoDB service
36
187
  */
@@ -42,7 +193,7 @@ export class ServiceManager {
42
193
  const directories = this.config.getDataDirectories();
43
194
 
44
195
  // Ensure data directory exists
45
- await plugins.smartfile.fs.ensureDir(directories.mongo);
196
+ await plugins.smartfs.directory(directories.mongo).recursive().create();
46
197
 
47
198
  const status = await this.docker.getStatus(containers.mongo);
48
199
 
@@ -141,7 +292,7 @@ export class ServiceManager {
141
292
  const directories = this.config.getDataDirectories();
142
293
 
143
294
  // Ensure data directory exists
144
- await plugins.smartfile.fs.ensureDir(directories.minio);
295
+ await plugins.smartfs.directory(directories.minio).recursive().create();
145
296
 
146
297
  const status = await this.docker.getStatus(containers.minio);
147
298
 
@@ -259,7 +410,103 @@ export class ServiceManager {
259
410
  logger.log('info', ` API: http://${config.S3_HOST}:${config.S3_PORT}`);
260
411
  logger.log('info', ` Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT} (login: ${config.S3_ACCESSKEY}/***)`);
261
412
  }
262
-
413
+
414
+ /**
415
+ * Start Elasticsearch service
416
+ */
417
+ public async startElasticsearch(): Promise<void> {
418
+ logger.log('note', '📦 Elasticsearch:');
419
+
420
+ const config = this.config.getConfig();
421
+ const containers = this.config.getContainerNames();
422
+ const directories = this.config.getDataDirectories();
423
+
424
+ // Ensure data directory exists
425
+ await plugins.smartfs.directory(directories.elasticsearch).recursive().create();
426
+
427
+ const status = await this.docker.getStatus(containers.elasticsearch);
428
+
429
+ switch (status) {
430
+ case 'running':
431
+ logger.log('ok', ' Already running ✓');
432
+ break;
433
+
434
+ case 'stopped':
435
+ // Check if port mapping matches config
436
+ const esPortMappings = await this.docker.getPortMappings(containers.elasticsearch);
437
+ if (esPortMappings && esPortMappings['9200'] !== config.ELASTICSEARCH_PORT) {
438
+ logger.log('note', ' Port configuration changed, recreating container...');
439
+ await this.docker.remove(containers.elasticsearch, true);
440
+ // Fall through to create new container
441
+ const success = await this.docker.run({
442
+ name: containers.elasticsearch,
443
+ image: 'elasticsearch:8.11.0',
444
+ ports: {
445
+ [`0.0.0.0:${config.ELASTICSEARCH_PORT}`]: '9200'
446
+ },
447
+ volumes: {
448
+ [directories.elasticsearch]: '/usr/share/elasticsearch/data'
449
+ },
450
+ environment: {
451
+ 'discovery.type': 'single-node',
452
+ 'xpack.security.enabled': 'true',
453
+ 'ELASTIC_PASSWORD': config.ELASTICSEARCH_PASS,
454
+ 'ES_JAVA_OPTS': '-Xms512m -Xmx512m'
455
+ },
456
+ restart: 'unless-stopped'
457
+ });
458
+
459
+ if (success) {
460
+ logger.log('ok', ' Recreated with new port ✓');
461
+ } else {
462
+ logger.log('error', ' Failed to recreate container');
463
+ }
464
+ } else {
465
+ // Ports match, just start the container
466
+ if (await this.docker.start(containers.elasticsearch)) {
467
+ logger.log('ok', ' Started ✓');
468
+ } else {
469
+ logger.log('error', ' Failed to start');
470
+ }
471
+ }
472
+ break;
473
+
474
+ case 'not_exists':
475
+ logger.log('note', ' Creating container...');
476
+
477
+ const success = await this.docker.run({
478
+ name: containers.elasticsearch,
479
+ image: 'elasticsearch:8.11.0',
480
+ ports: {
481
+ [`0.0.0.0:${config.ELASTICSEARCH_PORT}`]: '9200'
482
+ },
483
+ volumes: {
484
+ [directories.elasticsearch]: '/usr/share/elasticsearch/data'
485
+ },
486
+ environment: {
487
+ 'discovery.type': 'single-node',
488
+ 'xpack.security.enabled': 'true',
489
+ 'ELASTIC_PASSWORD': config.ELASTICSEARCH_PASS,
490
+ 'ES_JAVA_OPTS': '-Xms512m -Xmx512m'
491
+ },
492
+ restart: 'unless-stopped'
493
+ });
494
+
495
+ if (success) {
496
+ logger.log('ok', ' Created and started ✓');
497
+ } else {
498
+ logger.log('error', ' Failed to create container');
499
+ }
500
+ break;
501
+ }
502
+
503
+ logger.log('info', ` Container: ${containers.elasticsearch}`);
504
+ logger.log('info', ` Port: ${config.ELASTICSEARCH_PORT}`);
505
+ logger.log('info', ` Connection: ${config.ELASTICSEARCH_URL}`);
506
+ logger.log('info', ` Username: ${config.ELASTICSEARCH_USER}`);
507
+ logger.log('info', ` Password: ${config.ELASTICSEARCH_PASS}`);
508
+ }
509
+
263
510
  /**
264
511
  * Stop MongoDB service
265
512
  */
@@ -285,10 +532,10 @@ export class ServiceManager {
285
532
  */
286
533
  public async stopMinIO(): Promise<void> {
287
534
  logger.log('note', '📦 S3/MinIO:');
288
-
535
+
289
536
  const containers = this.config.getContainerNames();
290
537
  const status = await this.docker.getStatus(containers.minio);
291
-
538
+
292
539
  if (status === 'running') {
293
540
  if (await this.docker.stop(containers.minio)) {
294
541
  logger.log('ok', ' Stopped ✓');
@@ -299,7 +546,27 @@ export class ServiceManager {
299
546
  logger.log('note', ' Not running');
300
547
  }
301
548
  }
302
-
549
+
550
+ /**
551
+ * Stop Elasticsearch service
552
+ */
553
+ public async stopElasticsearch(): Promise<void> {
554
+ logger.log('note', '📦 Elasticsearch:');
555
+
556
+ const containers = this.config.getContainerNames();
557
+ const status = await this.docker.getStatus(containers.elasticsearch);
558
+
559
+ if (status === 'running') {
560
+ if (await this.docker.stop(containers.elasticsearch)) {
561
+ logger.log('ok', ' Stopped ✓');
562
+ } else {
563
+ logger.log('error', ' Failed to stop');
564
+ }
565
+ } else {
566
+ logger.log('note', ' Not running');
567
+ }
568
+ }
569
+
303
570
  /**
304
571
  * Show service status
305
572
  */
@@ -385,8 +652,36 @@ export class ServiceManager {
385
652
  }
386
653
  break;
387
654
  }
655
+
656
+ // Elasticsearch status
657
+ const esStatus = await this.docker.getStatus(containers.elasticsearch);
658
+ switch (esStatus) {
659
+ case 'running':
660
+ logger.log('ok', '📦 Elasticsearch: 🟢 Running');
661
+ logger.log('info', ` ├─ Container: ${containers.elasticsearch}`);
662
+ logger.log('info', ` ├─ Port: ${config.ELASTICSEARCH_PORT}`);
663
+ logger.log('info', ` ├─ Connection: ${config.ELASTICSEARCH_URL}`);
664
+ logger.log('info', ` └─ Credentials: ${config.ELASTICSEARCH_USER}/${config.ELASTICSEARCH_PASS}`);
665
+ break;
666
+ case 'stopped':
667
+ logger.log('note', '📦 Elasticsearch: 🟡 Stopped');
668
+ logger.log('info', ` ├─ Container: ${containers.elasticsearch}`);
669
+ logger.log('info', ` └─ Port: ${config.ELASTICSEARCH_PORT}`);
670
+ break;
671
+ case 'not_exists':
672
+ logger.log('info', '📦 Elasticsearch: ⚪ Not installed');
673
+ // Check port availability
674
+ const esPort = parseInt(config.ELASTICSEARCH_PORT);
675
+ const esAvailable = await helpers.isPortAvailable(esPort);
676
+ if (!esAvailable) {
677
+ logger.log('error', ` └─ ⚠️ Port ${esPort} is in use by another process`);
678
+ } else {
679
+ logger.log('info', ` └─ Port ${esPort} is available`);
680
+ }
681
+ break;
682
+ }
388
683
  }
389
-
684
+
390
685
  /**
391
686
  * Show configuration
392
687
  */
@@ -420,6 +715,15 @@ export class ServiceManager {
420
715
  logger.log('info', ` Data: ${this.config.getDataDirectories().minio}`);
421
716
  logger.log('info', ` Endpoint: ${config.S3_ENDPOINT}`);
422
717
  logger.log('info', ` Console URL: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`);
718
+
719
+ console.log();
720
+ logger.log('note', 'Elasticsearch:');
721
+ logger.log('info', ` Host: ${config.ELASTICSEARCH_HOST}:${config.ELASTICSEARCH_PORT}`);
722
+ logger.log('info', ` User: ${config.ELASTICSEARCH_USER}`);
723
+ logger.log('info', ' Password: ***');
724
+ logger.log('info', ` Container: ${this.config.getContainerNames().elasticsearch}`);
725
+ logger.log('info', ` Data: ${this.config.getDataDirectories().elasticsearch}`);
726
+ logger.log('info', ` Connection: ${config.ELASTICSEARCH_URL}`);
423
727
  }
424
728
 
425
729
  /**
@@ -477,16 +781,29 @@ export class ServiceManager {
477
781
  logger.log('note', 'S3/MinIO container is not running');
478
782
  }
479
783
  break;
480
-
784
+
785
+ case 'elasticsearch':
786
+ case 'es':
787
+ if (await this.docker.isRunning(containers.elasticsearch)) {
788
+ helpers.printHeader(`Elasticsearch Logs (last ${lines} lines)`);
789
+ const logs = await this.docker.logs(containers.elasticsearch, lines);
790
+ console.log(logs);
791
+ } else {
792
+ logger.log('note', 'Elasticsearch container is not running');
793
+ }
794
+ break;
795
+
481
796
  case 'all':
482
797
  case '':
483
798
  await this.showLogs('mongo', lines);
484
799
  console.log();
485
800
  await this.showLogs('minio', lines);
801
+ console.log();
802
+ await this.showLogs('elasticsearch', lines);
486
803
  break;
487
-
804
+
488
805
  default:
489
- logger.log('note', 'Usage: gitzone services logs [mongo|s3|all] [lines]');
806
+ logger.log('note', 'Usage: gitzone services logs [mongo|s3|elasticsearch|all] [lines]');
490
807
  break;
491
808
  }
492
809
  }
@@ -497,24 +814,40 @@ export class ServiceManager {
497
814
  public async removeContainers(): Promise<void> {
498
815
  const containers = this.config.getContainerNames();
499
816
  let removed = false;
500
-
817
+
501
818
  if (await this.docker.exists(containers.mongo)) {
502
819
  if (await this.docker.remove(containers.mongo, true)) {
503
820
  logger.log('ok', ' MongoDB container removed ✓');
504
821
  removed = true;
505
822
  }
506
823
  }
507
-
824
+
508
825
  if (await this.docker.exists(containers.minio)) {
509
826
  if (await this.docker.remove(containers.minio, true)) {
510
827
  logger.log('ok', ' S3/MinIO container removed ✓');
511
828
  removed = true;
512
829
  }
513
830
  }
514
-
831
+
832
+ if (await this.docker.exists(containers.elasticsearch)) {
833
+ if (await this.docker.remove(containers.elasticsearch, true)) {
834
+ logger.log('ok', ' Elasticsearch container removed ✓');
835
+ removed = true;
836
+ }
837
+ }
838
+
515
839
  if (!removed) {
516
840
  logger.log('note', ' No containers to remove');
517
841
  }
842
+
843
+ // Check if all containers are gone, then unregister from global registry
844
+ const mongoExists = await this.docker.exists(containers.mongo);
845
+ const minioExists = await this.docker.exists(containers.minio);
846
+ const esExists = await this.docker.exists(containers.elasticsearch);
847
+
848
+ if (!mongoExists && !minioExists && !esExists) {
849
+ await this.globalRegistry.unregisterProject(process.cwd());
850
+ }
518
851
  }
519
852
 
520
853
  /**
@@ -523,24 +856,60 @@ export class ServiceManager {
523
856
  public async cleanData(): Promise<void> {
524
857
  const directories = this.config.getDataDirectories();
525
858
  let cleaned = false;
526
-
527
- if (await plugins.smartfile.fs.fileExists(directories.mongo)) {
528
- await plugins.smartfile.fs.remove(directories.mongo);
859
+
860
+ if (await plugins.smartfs.directory(directories.mongo).exists()) {
861
+ await plugins.smartfs.directory(directories.mongo).recursive().delete();
529
862
  logger.log('ok', ' MongoDB data removed ✓');
530
863
  cleaned = true;
531
864
  }
532
-
533
- if (await plugins.smartfile.fs.fileExists(directories.minio)) {
534
- await plugins.smartfile.fs.remove(directories.minio);
865
+
866
+ if (await plugins.smartfs.directory(directories.minio).exists()) {
867
+ await plugins.smartfs.directory(directories.minio).recursive().delete();
535
868
  logger.log('ok', ' S3/MinIO data removed ✓');
536
869
  cleaned = true;
537
870
  }
538
-
871
+
872
+ if (await plugins.smartfs.directory(directories.elasticsearch).exists()) {
873
+ await plugins.smartfs.directory(directories.elasticsearch).recursive().delete();
874
+ logger.log('ok', ' Elasticsearch data removed ✓');
875
+ cleaned = true;
876
+ }
877
+
539
878
  if (!cleaned) {
540
879
  logger.log('note', ' No data to clean');
541
880
  }
542
881
  }
543
882
 
883
+ /**
884
+ * Configure which services are enabled
885
+ */
886
+ public async configureServices(): Promise<void> {
887
+ logger.log('note', 'Select which services to enable for this project:');
888
+ console.log();
889
+
890
+ const currentServices = this.enabledServices || ['mongodb', 'minio', 'elasticsearch'];
891
+
892
+ const smartinteract = new plugins.smartinteract.SmartInteract();
893
+ const response = await smartinteract.askQuestion({
894
+ name: 'services',
895
+ type: 'checkbox',
896
+ message: 'Which services do you want to enable?',
897
+ choices: [
898
+ { name: 'MongoDB', value: 'mongodb' },
899
+ { name: 'MinIO (S3)', value: 'minio' },
900
+ { name: 'Elasticsearch', value: 'elasticsearch' }
901
+ ],
902
+ default: currentServices
903
+ });
904
+
905
+ this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
906
+
907
+ // Save to npmextra.json
908
+ await this.saveServiceConfiguration(this.enabledServices);
909
+
910
+ logger.log('ok', '✅ Service configuration updated');
911
+ }
912
+
544
913
  /**
545
914
  * Reconfigure services with new ports
546
915
  */
@@ -551,20 +920,25 @@ export class ServiceManager {
551
920
 
552
921
  // Stop existing containers
553
922
  logger.log('note', '🛑 Stopping existing containers...');
554
-
923
+
555
924
  if (await this.docker.exists(containers.mongo)) {
556
925
  await this.docker.stop(containers.mongo);
557
926
  logger.log('ok', ' MongoDB stopped ✓');
558
927
  }
559
-
928
+
560
929
  if (await this.docker.exists(containers.minio)) {
561
930
  await this.docker.stop(containers.minio);
562
931
  logger.log('ok', ' S3/MinIO stopped ✓');
563
932
  }
564
-
933
+
934
+ if (await this.docker.exists(containers.elasticsearch)) {
935
+ await this.docker.stop(containers.elasticsearch);
936
+ logger.log('ok', ' Elasticsearch stopped ✓');
937
+ }
938
+
565
939
  // Reconfigure ports
566
940
  await this.config.reconfigurePorts();
567
-
941
+
568
942
  // Ask if user wants to restart services
569
943
  const smartinteract = new plugins.smartinteract.SmartInteract();
570
944
  const response = await smartinteract.askQuestion({
@@ -573,11 +947,10 @@ export class ServiceManager {
573
947
  message: 'Do you want to start services with new ports?',
574
948
  default: true
575
949
  });
576
-
950
+
577
951
  if (response.value) {
578
952
  console.log();
579
- await this.startMongoDB();
580
- await this.startMinIO();
953
+ await this.startAll();
581
954
  }
582
955
  }
583
956
  }
@@ -42,11 +42,15 @@ export const getRandomAvailablePort = async (): Promise<number> => {
42
42
  /**
43
43
  * Get the project name from package.json or directory
44
44
  */
45
- export const getProjectName = (): string => {
45
+ export const getProjectName = async (): Promise<string> => {
46
46
  try {
47
47
  const packageJsonPath = plugins.path.join(process.cwd(), 'package.json');
48
- if (plugins.smartfile.fs.fileExistsSync(packageJsonPath)) {
49
- const packageJson = plugins.smartfile.fs.toObjectSync(packageJsonPath);
48
+ if (await plugins.smartfs.file(packageJsonPath).exists()) {
49
+ const content = (await plugins.smartfs
50
+ .file(packageJsonPath)
51
+ .encoding('utf8')
52
+ .read()) as string;
53
+ const packageJson = JSON.parse(content);
50
54
  if (packageJson.name) {
51
55
  // Sanitize: @fin.cx/skr → fin-cx-skr
52
56
  return packageJson.name.replace(/@/g, '').replace(/[\/\.]/g, '-');
@@ -55,7 +59,7 @@ export const getProjectName = (): string => {
55
59
  } catch (error) {
56
60
  // Ignore errors and fall back to directory name
57
61
  }
58
-
62
+
59
63
  return plugins.path.basename(process.cwd());
60
64
  };
61
65