@git.zone/cli 2.0.0 → 2.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.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@git.zone/cli',
6
- version: '2.0.0',
6
+ version: '2.2.0',
7
7
  description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
8
8
  }
@@ -154,10 +154,11 @@ export const run = async (projectArg: Project) => {
154
154
  ];
155
155
 
156
156
  // check for dependencies
157
+ // Note: @push.rocks/tapbundle is deprecated - use @git.zone/tstest/tapbundle instead
157
158
  await ensureDependency(
158
159
  packageJson,
159
160
  'devDep',
160
- 'latest',
161
+ 'exclude',
161
162
  '@push.rocks/tapbundle',
162
163
  );
163
164
  await ensureDependency(
@@ -10,12 +10,6 @@ import { Project } from '../classes.project.js';
10
10
  export const run = async (project: Project) => {
11
11
  const templateModule = await import('../mod_template/index.js');
12
12
 
13
- // update tslint
14
- // getting template
15
- const tslintTemplate = await templateModule.getTemplate('tslint');
16
- await tslintTemplate.writeToDisk(paths.cwd);
17
- logger.log('info', 'Updated tslint.json!');
18
-
19
13
  // update vscode
20
14
  const vscodeTemplate = await templateModule.getTemplate('vscode');
21
15
  await vscodeTemplate.writeToDisk(paths.cwd);
@@ -0,0 +1,190 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { DockerContainer } from './classes.dockercontainer.js';
3
+ import { logger } from '../gitzone.logging.js';
4
+
5
+ export interface IRegisteredProject {
6
+ projectPath: string;
7
+ projectName: string;
8
+ containers: {
9
+ mongo?: string;
10
+ minio?: string;
11
+ elasticsearch?: string;
12
+ };
13
+ ports: {
14
+ mongo?: number;
15
+ s3?: number;
16
+ s3Console?: number;
17
+ elasticsearch?: number;
18
+ };
19
+ enabledServices: string[];
20
+ lastActive: number;
21
+ }
22
+
23
+ export interface IGlobalRegistryData {
24
+ projects: { [projectPath: string]: IRegisteredProject };
25
+ }
26
+
27
+ export class GlobalRegistry {
28
+ private static instance: GlobalRegistry | null = null;
29
+ private kvStore: plugins.npmextra.KeyValueStore<IGlobalRegistryData>;
30
+ private docker: DockerContainer;
31
+
32
+ private constructor() {
33
+ this.kvStore = new plugins.npmextra.KeyValueStore({
34
+ typeArg: 'userHomeDir',
35
+ identityArg: 'gitzone-services',
36
+ });
37
+ this.docker = new DockerContainer();
38
+ }
39
+
40
+ /**
41
+ * Get the singleton instance
42
+ */
43
+ public static getInstance(): GlobalRegistry {
44
+ if (!GlobalRegistry.instance) {
45
+ GlobalRegistry.instance = new GlobalRegistry();
46
+ }
47
+ return GlobalRegistry.instance;
48
+ }
49
+
50
+ /**
51
+ * Register or update a project in the global registry
52
+ */
53
+ public async registerProject(data: Omit<IRegisteredProject, 'lastActive'>): Promise<void> {
54
+ const allData = await this.kvStore.readAll();
55
+ const projects = allData.projects || {};
56
+
57
+ projects[data.projectPath] = {
58
+ ...data,
59
+ lastActive: Date.now(),
60
+ };
61
+
62
+ await this.kvStore.writeKey('projects', projects);
63
+ }
64
+
65
+ /**
66
+ * Remove a project from the registry
67
+ */
68
+ public async unregisterProject(projectPath: string): Promise<void> {
69
+ const allData = await this.kvStore.readAll();
70
+ const projects = allData.projects || {};
71
+
72
+ if (projects[projectPath]) {
73
+ delete projects[projectPath];
74
+ await this.kvStore.writeKey('projects', projects);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Update the lastActive timestamp for a project
80
+ */
81
+ public async touchProject(projectPath: string): Promise<void> {
82
+ const allData = await this.kvStore.readAll();
83
+ const projects = allData.projects || {};
84
+
85
+ if (projects[projectPath]) {
86
+ projects[projectPath].lastActive = Date.now();
87
+ await this.kvStore.writeKey('projects', projects);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get all registered projects
93
+ */
94
+ public async getAllProjects(): Promise<{ [path: string]: IRegisteredProject }> {
95
+ const allData = await this.kvStore.readAll();
96
+ return allData.projects || {};
97
+ }
98
+
99
+ /**
100
+ * Check if a project is registered
101
+ */
102
+ public async isRegistered(projectPath: string): Promise<boolean> {
103
+ const projects = await this.getAllProjects();
104
+ return !!projects[projectPath];
105
+ }
106
+
107
+ /**
108
+ * Get status of all containers across all registered projects
109
+ */
110
+ public async getGlobalStatus(): Promise<
111
+ Array<{
112
+ projectPath: string;
113
+ projectName: string;
114
+ containers: Array<{ name: string; status: string }>;
115
+ lastActive: number;
116
+ }>
117
+ > {
118
+ const projects = await this.getAllProjects();
119
+ const result: Array<{
120
+ projectPath: string;
121
+ projectName: string;
122
+ containers: Array<{ name: string; status: string }>;
123
+ lastActive: number;
124
+ }> = [];
125
+
126
+ for (const [path, project] of Object.entries(projects)) {
127
+ const containerStatuses: Array<{ name: string; status: string }> = [];
128
+
129
+ for (const containerName of Object.values(project.containers)) {
130
+ if (containerName) {
131
+ const status = await this.docker.getStatus(containerName);
132
+ containerStatuses.push({ name: containerName, status });
133
+ }
134
+ }
135
+
136
+ result.push({
137
+ projectPath: path,
138
+ projectName: project.projectName,
139
+ containers: containerStatuses,
140
+ lastActive: project.lastActive,
141
+ });
142
+ }
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Stop all containers across all registered projects
149
+ */
150
+ public async stopAll(): Promise<{ stopped: string[]; failed: string[] }> {
151
+ const projects = await this.getAllProjects();
152
+ const stopped: string[] = [];
153
+ const failed: string[] = [];
154
+
155
+ for (const project of Object.values(projects)) {
156
+ for (const containerName of Object.values(project.containers)) {
157
+ if (containerName) {
158
+ const status = await this.docker.getStatus(containerName);
159
+ if (status === 'running') {
160
+ if (await this.docker.stop(containerName)) {
161
+ stopped.push(containerName);
162
+ } else {
163
+ failed.push(containerName);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ return { stopped, failed };
171
+ }
172
+
173
+ /**
174
+ * Remove stale registry entries (projects that no longer exist on disk)
175
+ */
176
+ public async cleanup(): Promise<string[]> {
177
+ const projects = await this.getAllProjects();
178
+ const removed: string[] = [];
179
+
180
+ for (const projectPath of Object.keys(projects)) {
181
+ const exists = await plugins.smartfs.directory(projectPath).exists();
182
+ if (!exists) {
183
+ await this.unregisterProject(projectPath);
184
+ removed.push(projectPath);
185
+ }
186
+ }
187
+
188
+ return removed;
189
+ }
190
+ }
@@ -2,16 +2,19 @@ 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;
11
13
 
12
14
  constructor() {
13
15
  this.config = new ServiceConfiguration();
14
16
  this.docker = new DockerContainer();
17
+ this.globalRegistry = GlobalRegistry.getInstance();
15
18
  }
16
19
 
17
20
  /**
@@ -107,6 +110,31 @@ export class ServiceManager {
107
110
  return this.enabledServices.includes(service);
108
111
  }
109
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
+
110
138
  /**
111
139
  * Start all enabled services
112
140
  */
@@ -127,6 +155,9 @@ export class ServiceManager {
127
155
  await this.startElasticsearch();
128
156
  first = false;
129
157
  }
158
+
159
+ // Register with global registry
160
+ await this.registerWithGlobalRegistry();
130
161
  }
131
162
 
132
163
  /**
@@ -808,6 +839,15 @@ export class ServiceManager {
808
839
  if (!removed) {
809
840
  logger.log('note', ' No containers to remove');
810
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
+ }
811
851
  }
812
852
 
813
853
  /**
@@ -1,15 +1,25 @@
1
1
  import * as plugins from './mod.plugins.js';
2
2
  import * as helpers from './helpers.js';
3
3
  import { ServiceManager } from './classes.servicemanager.js';
4
+ import { GlobalRegistry } from './classes.globalregistry.js';
4
5
  import { logger } from '../gitzone.logging.js';
5
6
 
6
7
  export const run = async (argvArg: any) => {
8
+ const isGlobal = argvArg.g || argvArg.global;
9
+ const command = argvArg._[1] || 'help';
10
+
11
+ // Handle global commands first
12
+ if (isGlobal) {
13
+ await handleGlobalCommand(command);
14
+ return;
15
+ }
16
+
17
+ // Local project commands
7
18
  const serviceManager = new ServiceManager();
8
19
  await serviceManager.init();
9
-
10
- const command = argvArg._[1] || 'help';
20
+
11
21
  const service = argvArg._[2] || 'all';
12
-
22
+
13
23
  switch (command) {
14
24
  case 'start':
15
25
  await handleStart(serviceManager, service);
@@ -249,4 +259,175 @@ function showHelp() {
249
259
  logger.log('info', ' gitzone services config # Show configuration');
250
260
  logger.log('info', ' gitzone services compass # Get MongoDB Compass connection');
251
261
  logger.log('info', ' gitzone services logs elasticsearch # Show Elasticsearch logs');
262
+ console.log();
263
+
264
+ logger.log('note', 'Global Commands (-g/--global):');
265
+ logger.log('info', ' list -g List all registered projects');
266
+ logger.log('info', ' status -g Show status across all projects');
267
+ logger.log('info', ' stop -g Stop all containers across all projects');
268
+ logger.log('info', ' cleanup -g Remove stale registry entries');
269
+ console.log();
270
+
271
+ logger.log('note', 'Global Examples:');
272
+ logger.log('info', ' gitzone services list -g # List all registered projects');
273
+ logger.log('info', ' gitzone services status -g # Show global container status');
274
+ logger.log('info', ' gitzone services stop -g # Stop all (prompts for confirmation)');
275
+ }
276
+
277
+ // ==================== Global Command Handlers ====================
278
+
279
+ async function handleGlobalCommand(command: string) {
280
+ const globalRegistry = GlobalRegistry.getInstance();
281
+
282
+ switch (command) {
283
+ case 'list':
284
+ await handleGlobalList(globalRegistry);
285
+ break;
286
+
287
+ case 'status':
288
+ await handleGlobalStatus(globalRegistry);
289
+ break;
290
+
291
+ case 'stop':
292
+ await handleGlobalStop(globalRegistry);
293
+ break;
294
+
295
+ case 'cleanup':
296
+ await handleGlobalCleanup(globalRegistry);
297
+ break;
298
+
299
+ case 'help':
300
+ default:
301
+ showHelp();
302
+ break;
303
+ }
304
+ }
305
+
306
+ async function handleGlobalList(globalRegistry: GlobalRegistry) {
307
+ helpers.printHeader('Registered Projects (Global)');
308
+
309
+ const projects = await globalRegistry.getAllProjects();
310
+ const projectPaths = Object.keys(projects);
311
+
312
+ if (projectPaths.length === 0) {
313
+ logger.log('note', 'No projects registered');
314
+ return;
315
+ }
316
+
317
+ for (const path of projectPaths) {
318
+ const project = projects[path];
319
+ const lastActive = new Date(project.lastActive).toLocaleString();
320
+
321
+ console.log();
322
+ logger.log('ok', `📁 ${project.projectName}`);
323
+ logger.log('info', ` Path: ${project.projectPath}`);
324
+ logger.log('info', ` Services: ${project.enabledServices.join(', ')}`);
325
+ logger.log('info', ` Last Active: ${lastActive}`);
326
+ }
327
+ }
328
+
329
+ async function handleGlobalStatus(globalRegistry: GlobalRegistry) {
330
+ helpers.printHeader('Global Service Status');
331
+
332
+ const statuses = await globalRegistry.getGlobalStatus();
333
+
334
+ if (statuses.length === 0) {
335
+ logger.log('note', 'No projects registered');
336
+ return;
337
+ }
338
+
339
+ let runningCount = 0;
340
+ let totalContainers = 0;
341
+
342
+ for (const project of statuses) {
343
+ console.log();
344
+ logger.log('ok', `📁 ${project.projectName}`);
345
+ logger.log('info', ` Path: ${project.projectPath}`);
346
+
347
+ if (project.containers.length === 0) {
348
+ logger.log('note', ' No containers configured');
349
+ continue;
350
+ }
351
+
352
+ for (const container of project.containers) {
353
+ totalContainers++;
354
+ const statusIcon = container.status === 'running' ? '🟢' : container.status === 'exited' ? '🟡' : '⚪';
355
+ if (container.status === 'running') runningCount++;
356
+ logger.log('info', ` ${statusIcon} ${container.name}: ${container.status}`);
357
+ }
358
+ }
359
+
360
+ console.log();
361
+ logger.log('note', `Summary: ${runningCount}/${totalContainers} containers running across ${statuses.length} project(s)`);
362
+ }
363
+
364
+ async function handleGlobalStop(globalRegistry: GlobalRegistry) {
365
+ helpers.printHeader('Stop All Containers (Global)');
366
+
367
+ const statuses = await globalRegistry.getGlobalStatus();
368
+
369
+ // Count running containers
370
+ let runningCount = 0;
371
+ for (const project of statuses) {
372
+ for (const container of project.containers) {
373
+ if (container.status === 'running') runningCount++;
374
+ }
375
+ }
376
+
377
+ if (runningCount === 0) {
378
+ logger.log('note', 'No running containers found');
379
+ return;
380
+ }
381
+
382
+ logger.log('note', `Found ${runningCount} running container(s) across ${statuses.length} project(s)`);
383
+ console.log();
384
+
385
+ // Show what will be stopped
386
+ for (const project of statuses) {
387
+ const runningContainers = project.containers.filter(c => c.status === 'running');
388
+ if (runningContainers.length > 0) {
389
+ logger.log('info', `${project.projectName}:`);
390
+ for (const container of runningContainers) {
391
+ logger.log('info', ` • ${container.name}`);
392
+ }
393
+ }
394
+ }
395
+
396
+ console.log();
397
+ const shouldContinue = await plugins.smartinteract.SmartInteract.getCliConfirmation(
398
+ 'Stop all containers?',
399
+ false
400
+ );
401
+
402
+ if (!shouldContinue) {
403
+ logger.log('note', 'Cancelled');
404
+ return;
405
+ }
406
+
407
+ logger.log('note', 'Stopping all containers...');
408
+ const result = await globalRegistry.stopAll();
409
+
410
+ if (result.stopped.length > 0) {
411
+ logger.log('ok', `Stopped: ${result.stopped.join(', ')}`);
412
+ }
413
+ if (result.failed.length > 0) {
414
+ logger.log('error', `Failed to stop: ${result.failed.join(', ')}`);
415
+ }
416
+ }
417
+
418
+ async function handleGlobalCleanup(globalRegistry: GlobalRegistry) {
419
+ helpers.printHeader('Cleanup Registry (Global)');
420
+
421
+ logger.log('note', 'Checking for stale registry entries...');
422
+ const removed = await globalRegistry.cleanup();
423
+
424
+ if (removed.length === 0) {
425
+ logger.log('ok', 'No stale entries found');
426
+ return;
427
+ }
428
+
429
+ logger.log('ok', `Removed ${removed.length} stale entr${removed.length === 1 ? 'y' : 'ies'}:`);
430
+ for (const path of removed) {
431
+ logger.log('info', ` • ${path}`);
432
+ }
252
433
  }