@git.zone/cli 1.16.8 → 1.17.1

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 (40) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/gitzone.cli.js +8 -1
  3. package/dist_ts/mod_format/classes.baseformatter.js +12 -2
  4. package/dist_ts/mod_format/format.gitignore.js +27 -7
  5. package/dist_ts/mod_format/format.packagejson.js +3 -3
  6. package/dist_ts/mod_format/formatters/prettier.formatter.js +61 -37
  7. package/dist_ts/mod_format/index.js +1 -1
  8. package/dist_ts/mod_services/classes.dockercontainer.d.ts +68 -0
  9. package/dist_ts/mod_services/classes.dockercontainer.js +194 -0
  10. package/dist_ts/mod_services/classes.serviceconfiguration.d.ts +69 -0
  11. package/dist_ts/mod_services/classes.serviceconfiguration.js +195 -0
  12. package/dist_ts/mod_services/classes.servicemanager.d.ts +49 -0
  13. package/dist_ts/mod_services/classes.servicemanager.js +363 -0
  14. package/dist_ts/mod_services/helpers.d.ts +24 -0
  15. package/dist_ts/mod_services/helpers.js +108 -0
  16. package/dist_ts/mod_services/index.d.ts +1 -0
  17. package/dist_ts/mod_services/index.js +184 -0
  18. package/dist_ts/mod_services/mod.plugins.d.ts +7 -0
  19. package/dist_ts/mod_services/mod.plugins.js +8 -0
  20. package/dist_ts/plugins.d.ts +6 -1
  21. package/dist_ts/plugins.js +7 -2
  22. package/npmextra.json +1 -1
  23. package/package.json +29 -24
  24. package/readme.hints.md +8 -3
  25. package/readme.md +151 -17
  26. package/readme.plan.md +119 -168
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/gitzone.cli.ts +8 -0
  29. package/ts/mod_format/classes.baseformatter.ts +13 -1
  30. package/ts/mod_format/format.gitignore.ts +31 -6
  31. package/ts/mod_format/format.packagejson.ts +2 -2
  32. package/ts/mod_format/formatters/prettier.formatter.ts +76 -43
  33. package/ts/mod_format/index.ts +4 -1
  34. package/ts/mod_services/classes.dockercontainer.ts +227 -0
  35. package/ts/mod_services/classes.serviceconfiguration.ts +246 -0
  36. package/ts/mod_services/classes.servicemanager.ts +423 -0
  37. package/ts/mod_services/helpers.ts +123 -0
  38. package/ts/mod_services/index.ts +219 -0
  39. package/ts/mod_services/mod.plugins.ts +9 -0
  40. package/ts/plugins.ts +10 -0
package/ts/gitzone.cli.ts CHANGED
@@ -131,6 +131,14 @@ export let run = async () => {
131
131
  modHelpers.run(argvArg);
132
132
  });
133
133
 
134
+ /**
135
+ * manage development services (MongoDB, S3/MinIO)
136
+ */
137
+ gitzoneSmartcli.addCommand('services').subscribe(async (argvArg) => {
138
+ const modServices = await import('./mod_services/index.js');
139
+ await modServices.run(argvArg);
140
+ });
141
+
134
142
  // start parsing of the cli
135
143
  gitzoneSmartcli.startParse();
136
144
  return await done.promise;
@@ -53,7 +53,19 @@ export abstract class BaseFormatter {
53
53
  }
54
54
 
55
55
  protected async modifyFile(filepath: string, content: string): Promise<void> {
56
- await plugins.smartfile.memory.toFs(content, filepath);
56
+ // Validate filepath before writing
57
+ if (!filepath || filepath.trim() === '') {
58
+ throw new Error(`Invalid empty filepath in modifyFile`);
59
+ }
60
+
61
+ // Ensure we have a proper path with directory component
62
+ // If the path has no directory component (e.g., "package.json"), prepend "./"
63
+ let normalizedPath = filepath;
64
+ if (!plugins.path.parse(filepath).dir) {
65
+ normalizedPath = './' + filepath;
66
+ }
67
+
68
+ await plugins.smartfile.memory.toFs(content, normalizedPath);
57
69
  }
58
70
 
59
71
  protected async createFile(filepath: string, content: string): Promise<void> {
@@ -8,15 +8,40 @@ const gitignorePath = plugins.path.join(paths.cwd, './.gitignore');
8
8
 
9
9
  export const run = async (projectArg: Project) => {
10
10
  const gitignoreExists = await plugins.smartfile.fs.fileExists(gitignorePath);
11
- const templateModule = await import('../mod_template/index.js');
12
- const ciTemplate = await templateModule.getTemplate('gitignore');
11
+ let customContent = '';
12
+
13
13
  if (gitignoreExists) {
14
14
  // lets get the existing gitignore file
15
15
  const existingGitIgnoreString =
16
16
  plugins.smartfile.fs.toStringSync(gitignorePath);
17
- let customPart = existingGitIgnoreString.split('# custom\n')[1];
18
- customPart ? null : (customPart = '');
17
+
18
+ // Check for different custom section markers
19
+ const customMarkers = ['#------# custom', '# custom'];
20
+ for (const marker of customMarkers) {
21
+ const splitResult = existingGitIgnoreString.split(marker);
22
+ if (splitResult.length > 1) {
23
+ // Get everything after the marker (excluding the marker itself)
24
+ customContent = splitResult[1].trim();
25
+ break;
26
+ }
27
+ }
28
+ }
29
+
30
+ // Write the template
31
+ const templateModule = await import('../mod_template/index.js');
32
+ const ciTemplate = await templateModule.getTemplate('gitignore');
33
+ await ciTemplate.writeToDisk(paths.cwd);
34
+
35
+ // Append the custom content if it exists
36
+ if (customContent) {
37
+ const newGitignoreContent =
38
+ plugins.smartfile.fs.toStringSync(gitignorePath);
39
+ // The template already ends with "#------# custom", so just append the content
40
+ const finalContent =
41
+ newGitignoreContent.trimEnd() + '\n' + customContent + '\n';
42
+ await plugins.smartfile.fs.toFs(finalContent, gitignorePath);
43
+ logger.log('info', 'Updated .gitignore while preserving custom section!');
44
+ } else {
45
+ logger.log('info', 'Added a .gitignore!');
19
46
  }
20
- ciTemplate.writeToDisk(paths.cwd);
21
- logger.log('info', 'Added a .gitignore!');
22
47
  };
@@ -83,10 +83,10 @@ export const run = async (projectArg: Project) => {
83
83
  type: 'git',
84
84
  url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}.git`,
85
85
  };
86
- (packageJson.bugs = {
86
+ ((packageJson.bugs = {
87
87
  url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}/issues`,
88
88
  }),
89
- (packageJson.homepage = `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}#readme`);
89
+ (packageJson.homepage = `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}#readme`));
90
90
 
91
91
  // Check for module type
92
92
  if (!packageJson.type) {
@@ -29,8 +29,9 @@ export class PrettierFormatter extends BaseFormatter {
29
29
  'README.md',
30
30
  'changelog.md',
31
31
  'CHANGELOG.md',
32
- 'license',
33
- 'LICENSE',
32
+ // Skip files without extensions as prettier can't infer parser
33
+ // 'license',
34
+ // 'LICENSE',
34
35
  '*.md',
35
36
  ];
36
37
 
@@ -102,40 +103,26 @@ export class PrettierFormatter extends BaseFormatter {
102
103
  try {
103
104
  await this.preExecute();
104
105
 
105
- // Batch process files
106
- const batchSize = 10; // Process 10 files at a time
107
- const batches: IPlannedChange[][] = [];
106
+ logVerbose(`Processing ${changes.length} files sequentially`);
108
107
 
109
- for (let i = 0; i < changes.length; i += batchSize) {
110
- batches.push(changes.slice(i, i + batchSize));
111
- }
112
-
113
- logVerbose(
114
- `Processing ${changes.length} files in ${batches.length} batches`,
115
- );
116
-
117
- for (let i = 0; i < batches.length; i++) {
118
- const batch = batches[i];
108
+ // Process files sequentially to avoid prettier cache/state issues
109
+ for (let i = 0; i < changes.length; i++) {
110
+ const change = changes[i];
119
111
  logVerbose(
120
- `Processing batch ${i + 1}/${batches.length} (${batch.length} files)`,
112
+ `Processing file ${i + 1}/${changes.length}: ${change.path}`,
121
113
  );
122
114
 
123
- // Process batch in parallel
124
- const promises = batch.map(async (change) => {
125
- try {
126
- await this.applyChange(change);
127
- this.stats.recordFileOperation(this.name, change.type, true);
128
- } catch (error) {
129
- this.stats.recordFileOperation(this.name, change.type, false);
130
- logger.log(
131
- 'error',
132
- `Failed to format ${change.path}: ${error.message}`,
133
- );
134
- // Don't throw - continue with other files
135
- }
136
- });
137
-
138
- await Promise.all(promises);
115
+ try {
116
+ await this.applyChange(change);
117
+ this.stats.recordFileOperation(this.name, change.type, true);
118
+ } catch (error) {
119
+ this.stats.recordFileOperation(this.name, change.type, false);
120
+ logger.log(
121
+ 'error',
122
+ `Failed to format ${change.path}: ${error.message}`,
123
+ );
124
+ // Don't throw - continue with other files
125
+ }
139
126
  }
140
127
 
141
128
  await this.postExecute();
@@ -151,25 +138,71 @@ export class PrettierFormatter extends BaseFormatter {
151
138
  if (change.type !== 'modify') return;
152
139
 
153
140
  try {
141
+ // Validate the path before processing
142
+ if (!change.path || change.path.trim() === '') {
143
+ logger.log(
144
+ 'error',
145
+ `Invalid empty path in change: ${JSON.stringify(change)}`,
146
+ );
147
+ throw new Error('Invalid empty path');
148
+ }
149
+
154
150
  // Read current content
155
151
  const content = plugins.smartfile.fs.toStringSync(change.path);
156
152
 
157
153
  // Format with prettier
158
154
  const prettier = await import('prettier');
159
- const formatted = await prettier.format(content, {
160
- filepath: change.path,
161
- ...(await this.getPrettierConfig()),
162
- });
163
155
 
164
- // Only write if content actually changed
165
- if (formatted !== content) {
166
- await this.modifyFile(change.path, formatted);
167
- logVerbose(`Formatted ${change.path}`);
168
- } else {
169
- logVerbose(`No formatting changes for ${change.path}`);
156
+ // Skip files that prettier can't parse without explicit parser
157
+ const fileExt = plugins.path.extname(change.path).toLowerCase();
158
+ if (!fileExt || fileExt === '') {
159
+ // Files without extensions need explicit parser
160
+ logVerbose(
161
+ `Skipping ${change.path} - no file extension for parser inference`,
162
+ );
163
+ return;
164
+ }
165
+
166
+ try {
167
+ const formatted = await prettier.format(content, {
168
+ filepath: change.path,
169
+ ...(await this.getPrettierConfig()),
170
+ });
171
+
172
+ // Only write if content actually changed
173
+ if (formatted !== content) {
174
+ // Debug: log the path being written
175
+ logVerbose(`Writing formatted content to: ${change.path}`);
176
+ await this.modifyFile(change.path, formatted);
177
+ logVerbose(`Formatted ${change.path}`);
178
+ } else {
179
+ logVerbose(`No formatting changes for ${change.path}`);
180
+ }
181
+ } catch (prettierError) {
182
+ // Check if it's a parser error
183
+ if (
184
+ prettierError.message &&
185
+ prettierError.message.includes('No parser could be inferred')
186
+ ) {
187
+ logVerbose(`Skipping ${change.path} - ${prettierError.message}`);
188
+ return; // Skip this file silently
189
+ }
190
+ throw prettierError;
170
191
  }
171
192
  } catch (error) {
172
- logger.log('error', `Failed to format ${change.path}: ${error.message}`);
193
+ // Log the full error stack for debugging mkdir issues
194
+ if (error.message && error.message.includes('mkdir')) {
195
+ logger.log(
196
+ 'error',
197
+ `Failed to format ${change.path}: ${error.message}`,
198
+ );
199
+ logger.log('error', `Error stack: ${error.stack}`);
200
+ } else {
201
+ logger.log(
202
+ 'error',
203
+ `Failed to format ${change.path}: ${error.message}`,
204
+ );
205
+ }
173
206
  throw error;
174
207
  }
175
208
  }
@@ -185,5 +185,8 @@ export const handleListBackups = async (): Promise<void> => {
185
185
  };
186
186
 
187
187
  export const handleCleanBackups = async (): Promise<void> => {
188
- logger.log('info', 'Backup cleaning has been disabled - backup system removed');
188
+ logger.log(
189
+ 'info',
190
+ 'Backup cleaning has been disabled - backup system removed',
191
+ );
189
192
  };
@@ -0,0 +1,227 @@
1
+ import * as plugins from './mod.plugins.js';
2
+ import * as helpers from './helpers.js';
3
+ import { logger } from '../gitzone.logging.js';
4
+
5
+ export type ContainerStatus = 'running' | 'stopped' | 'not_exists';
6
+
7
+ export interface IDockerRunOptions {
8
+ name: string;
9
+ image: string;
10
+ ports?: { [key: string]: string };
11
+ volumes?: { [key: string]: string };
12
+ environment?: { [key: string]: string };
13
+ restart?: string;
14
+ command?: string;
15
+ }
16
+
17
+ export class DockerContainer {
18
+ private smartshell: plugins.smartshell.Smartshell;
19
+
20
+ constructor() {
21
+ this.smartshell = new plugins.smartshell.Smartshell({
22
+ executor: 'bash',
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Check if Docker is installed and available
28
+ */
29
+ public async checkDocker(): Promise<boolean> {
30
+ try {
31
+ const result = await this.smartshell.exec('docker --version');
32
+ return result.exitCode === 0;
33
+ } catch (error) {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get container status
40
+ */
41
+ public async getStatus(containerName: string): Promise<ContainerStatus> {
42
+ try {
43
+ // Check if running
44
+ const runningResult = await this.smartshell.exec(
45
+ `docker ps --format '{{.Names}}' | grep -q "^${containerName}$"`
46
+ );
47
+
48
+ if (runningResult.exitCode === 0) {
49
+ return 'running';
50
+ }
51
+
52
+ // Check if exists but stopped
53
+ const existsResult = await this.smartshell.exec(
54
+ `docker ps -a --format '{{.Names}}' | grep -q "^${containerName}$"`
55
+ );
56
+
57
+ if (existsResult.exitCode === 0) {
58
+ return 'stopped';
59
+ }
60
+
61
+ return 'not_exists';
62
+ } catch (error) {
63
+ return 'not_exists';
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Start a container
69
+ */
70
+ public async start(containerName: string): Promise<boolean> {
71
+ try {
72
+ const result = await this.smartshell.exec(`docker start ${containerName}`);
73
+ return result.exitCode === 0;
74
+ } catch (error) {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Stop a container
81
+ */
82
+ public async stop(containerName: string): Promise<boolean> {
83
+ try {
84
+ const result = await this.smartshell.exec(`docker stop ${containerName}`);
85
+ return result.exitCode === 0;
86
+ } catch (error) {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Remove a container
93
+ */
94
+ public async remove(containerName: string, force: boolean = false): Promise<boolean> {
95
+ try {
96
+ const forceFlag = force ? '-f' : '';
97
+ const result = await this.smartshell.exec(`docker rm ${forceFlag} ${containerName}`);
98
+ return result.exitCode === 0;
99
+ } catch (error) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Run a new container
106
+ */
107
+ public async run(options: IDockerRunOptions): Promise<boolean> {
108
+ let command = 'docker run -d';
109
+
110
+ // Add name
111
+ command += ` --name ${options.name}`;
112
+
113
+ // Add ports
114
+ if (options.ports) {
115
+ for (const [hostPort, containerPort] of Object.entries(options.ports)) {
116
+ command += ` -p ${hostPort}:${containerPort}`;
117
+ }
118
+ }
119
+
120
+ // Add volumes
121
+ if (options.volumes) {
122
+ for (const [hostPath, containerPath] of Object.entries(options.volumes)) {
123
+ command += ` -v "${hostPath}:${containerPath}"`;
124
+ }
125
+ }
126
+
127
+ // Add environment variables
128
+ if (options.environment) {
129
+ for (const [key, value] of Object.entries(options.environment)) {
130
+ command += ` -e ${key}="${value}"`;
131
+ }
132
+ }
133
+
134
+ // Add restart policy
135
+ if (options.restart) {
136
+ command += ` --restart ${options.restart}`;
137
+ }
138
+
139
+ // Add image
140
+ command += ` ${options.image}`;
141
+
142
+ // Add command if provided
143
+ if (options.command) {
144
+ command += ` ${options.command}`;
145
+ }
146
+
147
+ try {
148
+ const result = await this.smartshell.exec(command);
149
+ return result.exitCode === 0;
150
+ } catch (error) {
151
+ logger.log('error', `Failed to run container: ${error.message}`);
152
+ return false;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Execute a command in a running container
158
+ */
159
+ public async exec(containerName: string, command: string): Promise<string> {
160
+ try {
161
+ const result = await this.smartshell.exec(`docker exec ${containerName} ${command}`);
162
+ if (result.exitCode === 0) {
163
+ return result.stdout;
164
+ }
165
+ return '';
166
+ } catch (error) {
167
+ return '';
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Get container logs
173
+ */
174
+ public async logs(containerName: string, lines?: number): Promise<string> {
175
+ try {
176
+ const tailFlag = lines ? `--tail ${lines}` : '';
177
+ const result = await this.smartshell.exec(`docker logs ${tailFlag} ${containerName}`);
178
+ return result.stdout;
179
+ } catch (error) {
180
+ return `Error getting logs: ${error.message}`;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Check if a container exists
186
+ */
187
+ public async exists(containerName: string): Promise<boolean> {
188
+ const status = await this.getStatus(containerName);
189
+ return status !== 'not_exists';
190
+ }
191
+
192
+ /**
193
+ * Check if a container is running
194
+ */
195
+ public async isRunning(containerName: string): Promise<boolean> {
196
+ const status = await this.getStatus(containerName);
197
+ return status === 'running';
198
+ }
199
+
200
+ /**
201
+ * Wait for a container to be ready
202
+ */
203
+ public async waitForReady(containerName: string, maxAttempts: number = 30): Promise<boolean> {
204
+ for (let i = 0; i < maxAttempts; i++) {
205
+ if (await this.isRunning(containerName)) {
206
+ return true;
207
+ }
208
+ await plugins.smartdelay.delayFor(1000);
209
+ }
210
+ return false;
211
+ }
212
+
213
+ /**
214
+ * Get container information
215
+ */
216
+ public async inspect(containerName: string): Promise<any> {
217
+ try {
218
+ const result = await this.smartshell.exec(`docker inspect ${containerName}`);
219
+ if (result.exitCode === 0) {
220
+ return JSON.parse(result.stdout);
221
+ }
222
+ return null;
223
+ } catch (error) {
224
+ return null;
225
+ }
226
+ }
227
+ }