Compare commits
	
		
			49 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 64062e5c43 | |||
| bd22844280 | |||
| 366c4a0bc2 | |||
| 0d3b10bd00 | |||
| a41e3d5d2c | |||
| c45cff89de | |||
| 7bb43ad478 | |||
| 8dcaf1c631 | |||
| 422761806d | |||
| 31360240a9 | |||
| e338ee584f | |||
| 31d2e18830 | |||
| a162ddabbb | |||
| 5dfa1d72aa | |||
| 7074a19a7f | |||
| 5774fb4da2 | |||
| be45ce765d | |||
| 2a250b8823 | |||
| 9a436cb4be | |||
| 86782c39dd | |||
| fba3e9d2b0 | |||
| cc37f70185 | |||
| dbc1a1ba18 | |||
| ff57f8a322 | |||
| 968e67330d | |||
| 935ee20e83 | |||
| c205180991 | |||
| 4a53bc4abc | |||
| a86fb3bb8e | |||
| b187000ae4 | |||
| c715adfd6c | |||
| 7b9ebfdacb | |||
| 05b170cbac | |||
| b320af0b61 | |||
| 49e1ee1f39 | |||
| cef31cf1ff | |||
| 74ecdde1ac | |||
| 74a8229e43 | |||
| 859cbc733d | |||
| d32d47b706 | |||
| fd90cfe895 | |||
| c48f48fc8b | |||
| e21e7f0850 | |||
| 5f561527f9 | |||
| 9f5f568c3f | |||
| 39a31a4304 | |||
| b629a7d70b | |||
| 4003944139 | |||
| 83d374dffd | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -17,3 +17,5 @@ dist/
 | 
				
			|||||||
dist_*/
 | 
					dist_*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#------# custom
 | 
					#------# custom
 | 
				
			||||||
 | 
					.serena
 | 
				
			||||||
 | 
					test-output.json
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,4 +19,8 @@ node_modules/
 | 
				
			|||||||
dist/
 | 
					dist/
 | 
				
			||||||
dist_*/
 | 
					dist_*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# AI
 | 
				
			||||||
 | 
					.claude/
 | 
				
			||||||
 | 
					.serena/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#------# custom
 | 
					#------# custom
 | 
				
			||||||
@@ -2,4 +2,3 @@ runafter:
 | 
				
			|||||||
  - git add -A && git commit -m initial
 | 
					  - git add -A && git commit -m initial
 | 
				
			||||||
  - git push origin master
 | 
					  - git push origin master
 | 
				
			||||||
  - gitzone meta update
 | 
					  - gitzone meta update
 | 
				
			||||||
 
 | 
					 | 
				
			||||||
							
								
								
									
										273
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										273
									
								
								changelog.md
									
									
									
									
									
								
							@@ -1,18 +1,230 @@
 | 
				
			|||||||
# Changelog
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-11-02 - 1.19.1 - fix(dependencies)
 | 
				
			||||||
 | 
					Bump dependencies and add local Claude settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Bump devDependencies: @git.zone/tsbuild -> ^2.7.1, @git.zone/tsrun -> ^1.6.2, @git.zone/tstest -> ^2.7.0
 | 
				
			||||||
 | 
					- Upgrade runtime dependencies: @git.zone/tsdoc -> ^1.6.0; update @push.rocks packages (smartcli ^4.0.19, smartjson ^5.2.0, smartlog ^3.1.10, smartnetwork ^4.4.0, etc.)
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json (local project permissions/settings file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-10-23 - 1.19.0 - feat(mod_commit)
 | 
				
			||||||
 | 
					Add CLI UI helpers and improve commit workflow with progress, recommendations and summary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Introduce ts/mod_commit/mod.ui.ts: reusable CLI UI helpers (pretty headers, sections, AI recommendation box, step printer, commit summary and helpers for consistent messaging).
 | 
				
			||||||
 | 
					- Refactor ts/mod_commit/index.ts: use new UI functions to display AI recommendations, show step-by-step progress for baking commit info, generating changelog, staging, committing, bumping version and optional push; include commit SHA in final summary.
 | 
				
			||||||
 | 
					- Enhance ts/mod_commit/mod.helpers.ts: bumpProjectVersion now accepts currentStep/totalSteps to report progress and returns a consistent newVersion after handling npm/deno/both cases.
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json: local permissions configuration for development tooling.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-10-23 - 1.18.9 - fix(mod_commit)
 | 
				
			||||||
 | 
					Stage and commit deno.json when bumping/syncing versions and create/update git tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- bumpDenoVersion now creates a Smartshell instance and runs git add deno.json, git commit -m "v<newVersion>", and git tag v<newVersion> to persist the version bump
 | 
				
			||||||
 | 
					- syncVersionToDenoJson now stages deno.json, amends the npm version commit with --no-edit, and recreates the tag with -fa to keep package.json and deno.json in sync
 | 
				
			||||||
 | 
					- Added informative logger messages after creating commits and tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-10-23 - 1.18.8 - fix(mod_commit)
 | 
				
			||||||
 | 
					Improve commit workflow: detect project type and current branch; add robust version bump helpers for npm/deno
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add mod_commit/mod.helpers.ts with utilities: detectCurrentBranch(), detectProjectType(), bumpProjectVersion(), bumpDenoVersion(), bumpNpmVersion(), syncVersionToDenoJson(), and calculateNewVersion()
 | 
				
			||||||
 | 
					- Refactor ts/mod_commit/index.ts to use the new helpers: bumpProjectVersion(projectType, ... ) instead of a hard npm version call and push the actual current branch instead of hardcoding 'master'
 | 
				
			||||||
 | 
					- Support bumping versions for npm-only, deno-only, and hybrid (both) projects and synchronize versions from package.json to deno.json when applicable
 | 
				
			||||||
 | 
					- Improve branch detection with a fallback to 'master' and informative logging on detection failures
 | 
				
			||||||
 | 
					- Add local Claude settings file (.claude/settings.local.json) (editor/CI config) — no code behavior change but included in diff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-09-07 - 1.18.7 - fix(claude)
 | 
				
			||||||
 | 
					Add .claude local settings to whitelist dev tool permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json to configure allowed permissions for local AI/tooling helpers (Bash commands, WebFetch, and mcp_serena actions).
 | 
				
			||||||
 | 
					- Disable enableAllProjectMcpServers (set to false) to limit automatic project MCP server usage.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-09-07 - 1.18.6 - fix(deps)
 | 
				
			||||||
 | 
					Bump dependency versions and add local Claude settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Updated devDependencies: @git.zone/tsbuild ^2.6.4 → ^2.6.8, @git.zone/tstest ^2.3.4 → ^2.3.6, @push.rocks/smartfile ^11.2.5 → ^11.2.7
 | 
				
			||||||
 | 
					- Updated dependencies: @git.zone/tsdoc ^1.5.1 → ^1.5.2, @git.zone/tspublish ^1.10.1 → ^1.10.3, @push.rocks/smartlog ^3.1.8 → ^3.1.9, @push.rocks/smartnpm ^2.0.4 → ^2.0.6, @push.rocks/smartscaf ^4.0.17 → ^4.0.19
 | 
				
			||||||
 | 
					- Added .claude/settings.local.json to configure local Claude permissions/settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-17 - 1.18.5 - fix(dependencies)
 | 
				
			||||||
 | 
					Bump smartshell and smartscaf versions; add .claude local settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Update @push.rocks/smartshell from ^3.2.4 to ^3.3.0 in package.json
 | 
				
			||||||
 | 
					- Update @push.rocks/smartscaf from ^4.0.16 to ^4.0.17 in package.json
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json for local assistant permissions/configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-17 - 1.18.4 - fix(cli)
 | 
				
			||||||
 | 
					Update dependencies, add local Claude settings, and update gitignore template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Bump several dependencies: @git.zone/tsbuild -> ^2.6.4, @git.zone/tspublish -> ^1.10.1, @git.zone/tstest -> ^2.3.4, @push.rocks/smartfile -> ^11.2.5, @push.rocks/npmextra -> ^5.3.3, @push.rocks/smartchok -> ^1.1.1, @push.rocks/smartlog -> ^3.1.8, @push.rocks/smartpath -> ^6.0.0, prettier -> ^3.6.2
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json with local permissions configuration for AI tooling
 | 
				
			||||||
 | 
					- Update assets/templates/gitignore to ignore .claude/ and .serena/ directories
 | 
				
			||||||
 | 
					- Add pnpm onlyBuiltDependencies entries: esbuild and mongodb-memory-server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-16 - 1.18.3 - fix(services)
 | 
				
			||||||
 | 
					Simplify S3 endpoint handling in ServiceConfiguration to store host only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- S3_ENDPOINT now stores the raw host (e.g. 'localhost') instead of a full URL with protocol and port.
 | 
				
			||||||
 | 
					- Default .nogit/env.json creation uses the host-only S3_ENDPOINT.
 | 
				
			||||||
 | 
					- Sync/update logic (when syncing with Docker or reconfiguring ports) sets S3_ENDPOINT to the host only.
 | 
				
			||||||
 | 
					- Consumers that previously relied on S3_ENDPOINT containing protocol and port should now construct the full endpoint URL using S3_USESSL, S3_HOST and S3_PORT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-16 - 1.18.1 - fix(services)
 | 
				
			||||||
 | 
					Improve services and commit flow: stop AiDoc, use silent docker inspect, sync ports with logging, fix config loading, and bump deps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Ensure AiDoc is stopped after building commit recommendation to avoid resource leaks
 | 
				
			||||||
 | 
					- Use execSilent for `docker inspect` in DockerContainer to avoid shell noise and improve JSON parsing
 | 
				
			||||||
 | 
					- Sync Docker-exposed ports into service configuration with explicit notes (logs) when MongoDB / S3 ports are updated
 | 
				
			||||||
 | 
					- Fix synchronous config loading by removing an unnecessary await in ServiceConfiguration.loadConfig
 | 
				
			||||||
 | 
					- Bump dependencies: @push.rocks/smartshell -> ^3.2.4, @git.zone/tsdoc -> ^1.5.1
 | 
				
			||||||
 | 
					- Add pnpm.onlyBuiltDependencies for puppeteer and sharp to package.json
 | 
				
			||||||
 | 
					- Add local Claude settings file (.claude/settings.local.json) with development permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-16 - 1.18.0 - feat(services)
 | 
				
			||||||
 | 
					Add Docker port mapping sync and reconfigure workflow for local services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add getPortMappings to DockerContainer to extract port bindings from docker inspect output
 | 
				
			||||||
 | 
					- Sync existing container port mappings into .nogit/env.json when loading/creating service configuration
 | 
				
			||||||
 | 
					- Validate and automatically update ports only when containers are not present; preserve container ports when containers exist
 | 
				
			||||||
 | 
					- Recreate containers automatically if detected container port mappings differ from configuration (MongoDB and MinIO)
 | 
				
			||||||
 | 
					- Add reconfigure method and new CLI command to reassign ports and optionally restart services
 | 
				
			||||||
 | 
					- Improve status output to show configured ports and port availability information
 | 
				
			||||||
 | 
					- Minor helpers and imports updated (DockerContainer injected into ServiceConfiguration)
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json (local permissions config) to repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-15 - 1.17.5 - fix(services)
 | 
				
			||||||
 | 
					Update S3 credentials naming and add S3_ENDPOINT/S3_USESSL support for improved MinIO integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Replaced S3_USER/S3_PASS with S3_ACCESSKEY/S3_SECRETKEY in ServiceConfiguration
 | 
				
			||||||
 | 
					- Added S3_ENDPOINT field with automatic protocol selection based on S3_USESSL
 | 
				
			||||||
 | 
					- Introduced S3_USESSL boolean field for SSL/TLS configuration
 | 
				
			||||||
 | 
					- Updated ServiceManager logging to display new S3_USESSL configuration
 | 
				
			||||||
 | 
					- Added .claude/settings.local.json for local permission settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-15 - 1.17.4 - fix(services)
 | 
				
			||||||
 | 
					Update S3 credentials naming and add S3_ENDPOINT/S3_USESSL support for improved MinIO integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Replaced S3_USER/S3_PASS with S3_ACCESSKEY/S3_SECRETKEY in ServiceConfiguration
 | 
				
			||||||
 | 
					- Added S3_ENDPOINT field with automatic protocol selection based on S3_USESSL
 | 
				
			||||||
 | 
					- Added S3_USESSL boolean field for SSL/TLS configuration support
 | 
				
			||||||
 | 
					- Updated ServiceManager to use new credential names in container setup and logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-15 - 1.17.3 - fix(serviceconfig)
 | 
				
			||||||
 | 
					Update service configuration to include dynamic MongoDB connection string and add local permissions settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added .claude/settings.local.json for local permissions configuration
 | 
				
			||||||
 | 
					- Updated ServiceConfiguration to compute and update MONGODB_URL based on current config values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-15 - 1.17.2 - fix(ci-test-services)
 | 
				
			||||||
 | 
					Update CI/CD configurations, test settings, and Docker service for MongoDB.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json with updated permission settings
 | 
				
			||||||
 | 
					- Introduce new GitLab CI, VSCode launch and settings, and updated test configuration files (.gitignore, .npmrc, npmextra.json, package.json, qenv.yml, readme.md)
 | 
				
			||||||
 | 
					- Update test scripts in test/test and test/ts to improve project validation
 | 
				
			||||||
 | 
					- Fix MongoDB Docker container command by adding '--bind_ip_all' for proper network binding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-15 - 1.17.1 - fix(services)
 | 
				
			||||||
 | 
					Improve services module logging and enhance MongoDB Compass integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Refactored services module to use centralized logger from gitzone.logging.ts
 | 
				
			||||||
 | 
					- Automatically display MongoDB Compass connection string when starting services or checking status
 | 
				
			||||||
 | 
					- Removed custom printMessage wrapper in favor of standard logger.log() calls
 | 
				
			||||||
 | 
					- Consistent logging across all service commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-14 - 1.17.0 - feat(services)
 | 
				
			||||||
 | 
					Add comprehensive development services management for MongoDB and MinIO containers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Implemented `gitzone services` command for managing local development services
 | 
				
			||||||
 | 
					- Added MongoDB and MinIO (S3-compatible) container orchestration
 | 
				
			||||||
 | 
					- Smart port assignment (20000-30000 range) to avoid conflicts between projects
 | 
				
			||||||
 | 
					- Project-specific container names for complete isolation
 | 
				
			||||||
 | 
					- Data persistence in `.nogit/` directories
 | 
				
			||||||
 | 
					- MongoDB Compass connection string generation with network IP detection
 | 
				
			||||||
 | 
					- Auto-configuration via `.nogit/env.json` with secure defaults
 | 
				
			||||||
 | 
					- Commands: start, stop, restart, status, config, compass, logs, remove, clean
 | 
				
			||||||
 | 
					- Interactive confirmations for destructive operations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.10 - fix(format)
 | 
				
			||||||
 | 
					Improve concurrency control in caching and rollback modules, refine gitignore custom section handling, and enhance Prettier file processing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added mutex locking in ChangeCache and RollbackManager to prevent race conditions during manifest updates
 | 
				
			||||||
 | 
					- Updated gitignore logic to detect and preserve custom sections
 | 
				
			||||||
 | 
					- Enhanced Prettier batching and file formatting for better performance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.9 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Improve concurrency control in cache and rollback modules, refine gitignore custom section handling, and enhance Prettier file processing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added mutex locking in ChangeCache and RollbackManager to prevent race conditions during manifest updates
 | 
				
			||||||
 | 
					- Updated gitignore logic to detect and preserve existing custom sections from various markers
 | 
				
			||||||
 | 
					- Simplified Prettier formatter to process files sequentially, skip files without extensions, and log detailed status
 | 
				
			||||||
 | 
					- Minor refactoring in base formatter and tsconfig file updates for improved reliability
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.8 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Improve concurrency control in cache and rollback management with mutex locking and refine formatting details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added 'withMutex' functions in ChangeCache and RollbackManager to synchronize file I/O operations
 | 
				
			||||||
 | 
					- Introduced static mutex maps to prevent race conditions during manifest updates
 | 
				
			||||||
 | 
					- Fixed minor formatting issues in commit info and package.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.7 - fix(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Improve formatting, logging, and rollback integrity in core modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add .claude/settings.local.json with defined permissions for allowed commands
 | 
				
			||||||
 | 
					- Standardize formatting in package.json, commit info, and configuration files
 | 
				
			||||||
 | 
					- Refactor rollback manager to use atomic manifest writes and validate manifest structure
 | 
				
			||||||
 | 
					- Enhance logging messages and overall code clarity in CLI and commit modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.6 - fix(changecache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Improve cache manifest validation and atomic file writes; add local settings and overrides
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add manifest structure validation and default fallback in getManifest
 | 
				
			||||||
 | 
					- Implement atomic write in saveManifest using a temporary file and rename strategy
 | 
				
			||||||
 | 
					- Enhance error handling and cleanup for corrupted manifest files
 | 
				
			||||||
 | 
					- Introduce new .claude/settings.local.json for project-specific permission configuration
 | 
				
			||||||
 | 
					- Add an empty assets/overrides.json file for future overrides
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.5 - fix(prettier)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Improve file selection in Prettier formatter, remove legacy package overrides, and update CI template indentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added .claude/settings.local.json with updated permission settings for local commands
 | 
				
			||||||
 | 
					- Removed unnecessary overrides from assets/overrides.json and cleared packageManager overrides in package.json
 | 
				
			||||||
 | 
					- Adjusted CI template files (ci_default_gitlab, ci_default_private_gitlab, ci_docker_gitlab) for consistent indentation and formatting
 | 
				
			||||||
 | 
					- Refined Prettier formatter logic by defining include directories, root config files, and filtering duplicates instead of manual exclusion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.4 - fix(prettier)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Improve file exclusion in the Prettier formatter to skip unnecessary files and directories.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added exclusion patterns for node_modules, .git, dist, .nogit, coverage, .nyc_output, vendor, bower_components, jspm_packages, and minified files.
 | 
				
			||||||
 | 
					- Optimized filtering logic to ensure only valid files are processed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2025-08-08 - 1.16.3 - fix(changecache/prettier)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Skip directories during file processing to prevent errors in changecache and prettier formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Removed unnecessary await on synchronous file reads in changecache
 | 
				
			||||||
 | 
					- Added directory checks in changecache to immediately skip directories
 | 
				
			||||||
 | 
					- Filtered out directories in prettier formatter to avoid processing non-files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-08-07 - 1.16.2 - fix(format)
 | 
					## 2025-08-07 - 1.16.2 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fix format command confirmation prompt to correctly check user response
 | 
					Fix format command confirmation prompt to correctly check user response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Fixed bug where format command always showed "cancelled" even when user confirmed
 | 
					- Fixed bug where format command always showed "cancelled" even when user confirmed
 | 
				
			||||||
- Changed response check from `response.proceed` to `response.value` for SmartInteract compatibility
 | 
					- Changed response check from `response.proceed` to `response.value` for SmartInteract compatibility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-08-04 - 1.16.1 - fix(package/config)
 | 
					## 2025-08-04 - 1.16.1 - fix(package/config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Move smartdiff dependency to runtime and add local bash permissions settings
 | 
					Move smartdiff dependency to runtime and add local bash permissions settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Moved '@push.rocks/smartdiff' from devDependencies to dependencies in package.json
 | 
					- Moved '@push.rocks/smartdiff' from devDependencies to dependencies in package.json
 | 
				
			||||||
- Added .claude/settings.local.json with allowed bash commands (grep, mkdir, find, ls)
 | 
					- Added .claude/settings.local.json with allowed bash commands (grep, mkdir, find, ls)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-05-19 - 1.16.0 - feat(format)
 | 
					## 2025-05-19 - 1.16.0 - feat(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Enhance format module with rollback, diff reporting, and improved parallel execution
 | 
					Enhance format module with rollback, diff reporting, and improved parallel execution
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Implemented rollback functionality with backup management and automatic rollback on error
 | 
					- Implemented rollback functionality with backup management and automatic rollback on error
 | 
				
			||||||
@@ -23,12 +235,14 @@ Enhance format module with rollback, diff reporting, and improved parallel execu
 | 
				
			|||||||
- Updated package.json to include new dependency '@push.rocks/smartdiff'
 | 
					- Updated package.json to include new dependency '@push.rocks/smartdiff'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-05-14 - 1.15.5 - fix(dependencies)
 | 
					## 2025-05-14 - 1.15.5 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update @git.zone/tsdoc to ^1.5.0 and @types/node to ^22.15.18
 | 
					Update @git.zone/tsdoc to ^1.5.0 and @types/node to ^22.15.18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Bumped @git.zone/tsdoc from ^1.4.5 to ^1.5.0
 | 
					- Bumped @git.zone/tsdoc from ^1.4.5 to ^1.5.0
 | 
				
			||||||
- Bumped @types/node from ^22.15.17 to ^22.15.18
 | 
					- Bumped @types/node from ^22.15.17 to ^22.15.18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-05-13 - 1.15.4 - fix(package.json)
 | 
					## 2025-05-13 - 1.15.4 - fix(package.json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update dependency versions: bump @git.zone/tsdoc, @push.rocks/lik, @push.rocks/smartlog, and @types/node to their latest releases
 | 
					Update dependency versions: bump @git.zone/tsdoc, @push.rocks/lik, @push.rocks/smartlog, and @types/node to their latest releases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Upgrade @git.zone/tsdoc from ^1.4.4 to ^1.4.5
 | 
					- Upgrade @git.zone/tsdoc from ^1.4.4 to ^1.4.5
 | 
				
			||||||
@@ -37,6 +251,7 @@ Update dependency versions: bump @git.zone/tsdoc, @push.rocks/lik, @push.rocks/s
 | 
				
			|||||||
- Upgrade @types/node from ^22.14.1 to ^22.15.17
 | 
					- Upgrade @types/node from ^22.14.1 to ^22.15.17
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-04-15 - 1.15.3 - fix(deps)
 | 
					## 2025-04-15 - 1.15.3 - fix(deps)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
update dependency versions and improve website template variable handling
 | 
					update dependency versions and improve website template variable handling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Bumped @git.zone/tsbuild from ^2.2.1 to ^2.3.2 and @types/node to ^22.14.1
 | 
					- Bumped @git.zone/tsbuild from ^2.2.1 to ^2.3.2 and @types/node to ^22.14.1
 | 
				
			||||||
@@ -44,56 +259,65 @@ update dependency versions and improve website template variable handling
 | 
				
			|||||||
- Refactored website template update to correctly supply variables with added logging
 | 
					- Refactored website template update to correctly supply variables with added logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-04-15 - 1.15.2 - fix(website_update)
 | 
					## 2025-04-15 - 1.15.2 - fix(website_update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Await supplyVariables call in website update template
 | 
					Await supplyVariables call in website update template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Changed website template update to properly await the supplyVariables method
 | 
					- Changed website template update to properly await the supplyVariables method
 | 
				
			||||||
- Ensured asynchronous consistency in updating website template variables
 | 
					- Ensured asynchronous consistency in updating website template variables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-04-15 - 1.15.1 - fix(cli)
 | 
					## 2025-04-15 - 1.15.1 - fix(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Refresh internal CLI tooling and configuration for consistency.
 | 
					Refresh internal CLI tooling and configuration for consistency.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
## 2025-04-15 - 1.15.0 - feat(config/template)
 | 
					## 2025-04-15 - 1.15.0 - feat(config/template)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add assetbrokerUrl and legalUrl fields to module config and update website template to supply these values
 | 
					Add assetbrokerUrl and legalUrl fields to module config and update website template to supply these values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added assetbrokerUrl and legalUrl properties in ts/classes.gitzoneconfig.ts
 | 
					- Added assetbrokerUrl and legalUrl properties in ts/classes.gitzoneconfig.ts
 | 
				
			||||||
- Updated ts/mod_format/format.templates.ts to pass assetbrokerUrl and legalUrl to website template
 | 
					- Updated ts/mod_format/format.templates.ts to pass assetbrokerUrl and legalUrl to website template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-04-15 - 1.14.1 - fix(package.json)
 | 
					## 2025-04-15 - 1.14.1 - fix(package.json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add packageManager field to specify pnpm version for consistent package management
 | 
					Add packageManager field to specify pnpm version for consistent package management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Inserted packageManager property in package.json with pnpm version info to ensure reproducible dependency installs
 | 
					- Inserted packageManager property in package.json with pnpm version info to ensure reproducible dependency installs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-04-15 - 1.14.0 - feat(tsconfig_update)
 | 
					## 2025-04-15 - 1.14.0 - feat(tsconfig_update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add runafter directive to trigger gitzone format after tsconfig update
 | 
					Add runafter directive to trigger gitzone format after tsconfig update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added runafter configuration in assets/templates/tsconfig_update/.smartscaf.yml to automate formatting task
 | 
					- Added runafter configuration in assets/templates/tsconfig_update/.smartscaf.yml to automate formatting task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-03-07 - 1.13.1 - fix(cli)
 | 
					## 2025-03-07 - 1.13.1 - fix(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Improve commit message logging
 | 
					Improve commit message logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated logging to display recommended next commit details.
 | 
					- Updated logging to display recommended next commit details.
 | 
				
			||||||
- Enabled interactive prompt for choosing commit type and scope.
 | 
					- Enabled interactive prompt for choosing commit type and scope.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-02-28 - 1.13.0 - feat(templates)
 | 
					## 2025-02-28 - 1.13.0 - feat(templates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Updated and added new TypeScript template files for npm projects
 | 
					Updated and added new TypeScript template files for npm projects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added new paths.ts and plugins.ts template files for npm projects.
 | 
					- Added new paths.ts and plugins.ts template files for npm projects.
 | 
				
			||||||
- Removed outdated some.plugins.ts template file.
 | 
					- Removed outdated some.plugins.ts template file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-02-25 - 1.12.8 - fix(metadata)
 | 
					## 2025-02-25 - 1.12.8 - fix(metadata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Updated package and npmextra json description and keywords for enhanced development workflow clarity
 | 
					Updated package and npmextra json description and keywords for enhanced development workflow clarity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated the description in package.json to focus on project setup and management.
 | 
					- Updated the description in package.json to focus on project setup and management.
 | 
				
			||||||
- Aligned the keywords in both package.json and npmextra.json to include more relevant terms such as gitzone utilities, template management, and CI/CD.
 | 
					- Aligned the keywords in both package.json and npmextra.json to include more relevant terms such as gitzone utilities, template management, and CI/CD.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-02-25 - 1.12.7 - fix(meta)
 | 
					## 2025-02-25 - 1.12.7 - fix(meta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fix issues in project metadata and configuration.
 | 
					Fix issues in project metadata and configuration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated package metadata to ensure accurate project description and licensing.
 | 
					- Updated package metadata to ensure accurate project description and licensing.
 | 
				
			||||||
- Ensured npm access level configuration consistency within npmextra.json.
 | 
					- Ensured npm access level configuration consistency within npmextra.json.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-02-25 - 1.12.7 - fix(ci)
 | 
					## 2025-02-25 - 1.12.7 - fix(ci)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Updated dependencies and added CI/CD workflows.
 | 
					Updated dependencies and added CI/CD workflows.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated several dependencies in package.json for compatibility and security.
 | 
					- Updated several dependencies in package.json for compatibility and security.
 | 
				
			||||||
@@ -102,6 +326,7 @@ Updated dependencies and added CI/CD workflows.
 | 
				
			|||||||
- Ensured consistent formatting with Prettier and TypeScript configurations.
 | 
					- Ensured consistent formatting with Prettier and TypeScript configurations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-29 - 1.12.6 - fix(project)
 | 
					## 2025-01-29 - 1.12.6 - fix(project)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Minor fixes and cleanup
 | 
					Minor fixes and cleanup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Removed outdated pages/ directory entry in .gitignore.
 | 
					- Removed outdated pages/ directory entry in .gitignore.
 | 
				
			||||||
@@ -110,6 +335,7 @@ Minor fixes and cleanup
 | 
				
			|||||||
- Fixed formatting issues across various TypeScript files.
 | 
					- Fixed formatting issues across various TypeScript files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-29 - 1.12.5 - fix(cli)
 | 
					## 2025-01-29 - 1.12.5 - fix(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Initial implementation of CLI utility with project management features
 | 
					Initial implementation of CLI utility with project management features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Integration of various plugins for logging, command-line interactions, and project management.
 | 
					- Integration of various plugins for logging, command-line interactions, and project management.
 | 
				
			||||||
@@ -117,34 +343,40 @@ Initial implementation of CLI utility with project management features
 | 
				
			|||||||
- Implement commands for packaging, versioning, and deprecating npm packages.
 | 
					- Implement commands for packaging, versioning, and deprecating npm packages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-29 - 1.12.2 - fix(format)
 | 
					## 2025-01-29 - 1.12.2 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add overrides for peek-readable in package.json formatting
 | 
					Add overrides for peek-readable in package.json formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added a URL correction in the packageJson repository information.
 | 
					- Added a URL correction in the packageJson repository information.
 | 
				
			||||||
- Introduced support for pnpm overrides by including an `overrides.json` file.
 | 
					- Introduced support for pnpm overrides by including an `overrides.json` file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-18 - 1.12.1 - fix(dependencies)
 | 
					## 2025-01-18 - 1.12.1 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update various package dependencies and Dockerfile base image
 | 
					Update various package dependencies and Dockerfile base image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated Dockerfile base image from 'alpinenpmci' to 'alpine_npmci'.
 | 
					- Updated Dockerfile base image from 'alpinenpmci' to 'alpine_npmci'.
 | 
				
			||||||
- Upgraded @git.zone/tsbuild, @git.zone/tsrun, @git.zone/tsdoc, and other dependencies to their latest versions.
 | 
					- Upgraded @git.zone/tsbuild, @git.zone/tsrun, @git.zone/tsdoc, and other dependencies to their latest versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-17 - 1.12.0 - feat(build)
 | 
					## 2025-01-17 - 1.12.0 - feat(build)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update TypeScript configuration to support emit decorator metadata
 | 
					Update TypeScript configuration to support emit decorator metadata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added emitDecoratorMetadata to the tsconfig.json template in assets/templates/tsconfig_update.
 | 
					- Added emitDecoratorMetadata to the tsconfig.json template in assets/templates/tsconfig_update.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-08 - 1.11.0 - feat(cli)
 | 
					## 2025-01-08 - 1.11.0 - feat(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add Docker command for cleaning up Docker system and extend deprecation command for multiple registries
 | 
					Add Docker command for cleaning up Docker system and extend deprecation command for multiple registries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added a new command 'docker' to handle Docker system cleanup operations.
 | 
					- Added a new command 'docker' to handle Docker system cleanup operations.
 | 
				
			||||||
- Improved the 'deprecate' command to support deprecating packages across multiple npm registry URLs.
 | 
					- Improved the 'deprecate' command to support deprecating packages across multiple npm registry URLs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-01 - 1.10.10 - fix(templates)
 | 
					## 2025-01-01 - 1.10.10 - fix(templates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Corrected typo in template file comment
 | 
					Corrected typo in template file comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Fixed repeated comment in the template file for services under 'assets/templates/service/ts/some.plugins.ts'.
 | 
					- Fixed repeated comment in the template file for services under 'assets/templates/service/ts/some.plugins.ts'.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-01 - 1.10.9 - fix(templates)
 | 
					## 2025-01-01 - 1.10.9 - fix(templates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Correct template file paths and organization for service projects
 | 
					Correct template file paths and organization for service projects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Moved 'some.classes.some.ts' to 'classes.some.ts'
 | 
					- Moved 'some.classes.some.ts' to 'classes.some.ts'
 | 
				
			||||||
@@ -152,60 +384,70 @@ Correct template file paths and organization for service projects
 | 
				
			|||||||
- Resolved incorrect import paths in service templates
 | 
					- Resolved incorrect import paths in service templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2025-01-01 - 1.10.8 - fix(assets/templates)
 | 
					## 2025-01-01 - 1.10.8 - fix(assets/templates)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update CI template configurations to use module.githost
 | 
					Update CI template configurations to use module.githost
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Replaced occurrences of {{git.host}} with {{module.githost}} in CI workflow files
 | 
					- Replaced occurrences of {{git.host}} with {{module.githost}} in CI workflow files
 | 
				
			||||||
- Updated package dependencies for service template
 | 
					- Updated package dependencies for service template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-12-26 - 1.10.7 - fix(assets)
 | 
					## 2024-12-26 - 1.10.7 - fix(assets)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Correct URLs in templates and fix TypeScript declaration
 | 
					Correct URLs in templates and fix TypeScript declaration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated incorrect URLs in Dockerfile templates to 'host.today'.
 | 
					- Updated incorrect URLs in Dockerfile templates to 'host.today'.
 | 
				
			||||||
- Fixed type declaration for 'TemplateResult' in header.ts file.
 | 
					- Fixed type declaration for 'TemplateResult' in header.ts file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-12-08 - 1.10.6 - fix(ci)
 | 
					## 2024-12-08 - 1.10.6 - fix(ci)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Corrected Docker image URL in CI templates
 | 
					Corrected Docker image URL in CI templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated Docker image URL from 'code.foss.global/hosttoday' to 'code.foss.global/host.today' in default_nottags.yaml and default_tags.yaml.
 | 
					- Updated Docker image URL from 'code.foss.global/hosttoday' to 'code.foss.global/host.today' in default_nottags.yaml and default_tags.yaml.
 | 
				
			||||||
- Adjusted gitignore template to include a custom section delineation.
 | 
					- Adjusted gitignore template to include a custom section delineation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-12-02 - 1.10.5 - fix(assets)
 | 
					## 2024-12-02 - 1.10.5 - fix(assets)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update .gitignore template to remove pages directory
 | 
					Update .gitignore template to remove pages directory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Removed 'pages/' from the ignored directories in the .gitignore template.
 | 
					- Removed 'pages/' from the ignored directories in the .gitignore template.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-11-05 - 1.10.4 - fix(mod_format)
 | 
					## 2024-11-05 - 1.10.4 - fix(mod_format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Correct file extension for TypeScript path configuration
 | 
					Correct file extension for TypeScript path configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Fixed the TypeScript configuration to use correct file extensions for module subdirectories.
 | 
					- Fixed the TypeScript configuration to use correct file extensions for module subdirectories.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-10-27 - 1.10.3 - fix(mod_format)
 | 
					## 2024-10-27 - 1.10.3 - fix(mod_format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Reorder TypeScript formatting steps in mod_format module
 | 
					Reorder TypeScript formatting steps in mod_format module
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Moved TypeScript configuration formatting earlier in the sequence for better logical consistency.
 | 
					- Moved TypeScript configuration formatting earlier in the sequence for better logical consistency.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-10-27 - 1.10.2 - fix(format)
 | 
					## 2024-10-27 - 1.10.2 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add logging for tsconfig.json formatting
 | 
					Add logging for tsconfig.json formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added an info log message for tsconfig.json formatting in format.tsconfig.ts.
 | 
					- Added an info log message for tsconfig.json formatting in format.tsconfig.ts.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-10-27 - 1.10.1 - fix(format)
 | 
					## 2024-10-27 - 1.10.1 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fixed async issue in tsconfig module lookup and corrected property access
 | 
					Fixed async issue in tsconfig module lookup and corrected property access
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
## 2024-10-27 - 1.10.0 - feat(mod_format)
 | 
					## 2024-10-27 - 1.10.0 - feat(mod_format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Add support for tsconfig.json formatting
 | 
					Add support for tsconfig.json formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added a new script to format tsconfig.json.
 | 
					- Added a new script to format tsconfig.json.
 | 
				
			||||||
- Updated package.json to include `@git.zone/tspublish` as a dependency.
 | 
					- Updated package.json to include `@git.zone/tspublish` as a dependency.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-10-23 - 1.9.126 - fix(format)
 | 
					## 2024-10-23 - 1.9.126 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Remove redundant package.json property checks
 | 
					Remove redundant package.json property checks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Removed property checks for `main`, `typings`, and `browserslist` from format.packagejson.ts
 | 
					- Removed property checks for `main`, `typings`, and `browserslist` from format.packagejson.ts
 | 
				
			||||||
- This change streamlines the formatting process by removing unnecessary exits
 | 
					- This change streamlines the formatting process by removing unnecessary exits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-09-29 - 1.9.125 - fix(cli)
 | 
					## 2024-09-29 - 1.9.125 - fix(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fix package version configuration and formatting issues
 | 
					Fix package version configuration and formatting issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated metadata fields in package.json (repository URL, bugs URL, and homepage).
 | 
					- Updated metadata fields in package.json (repository URL, bugs URL, and homepage).
 | 
				
			||||||
@@ -213,15 +455,17 @@ Fix package version configuration and formatting issues
 | 
				
			|||||||
- Added missing Prettier default TypeScript and Markdown configurations.
 | 
					- Added missing Prettier default TypeScript and Markdown configurations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-09-27 - 1.9.124 - fix(cli)
 | 
					## 2024-09-27 - 1.9.124 - fix(cli)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Ensured proper existence and initialization of readme files
 | 
					Ensured proper existence and initialization of readme files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Ensured readme.md and readme.hints.md files are created and initialized if they do not exist.
 | 
					- Ensured readme.md and readme.hints.md files are created and initialized if they do not exist.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-09-27 - 1.9.123 - fix(core)
 | 
					## 2024-09-27 - 1.9.123 - fix(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
No changes detected
 | 
					No changes detected
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
## 2024-09-27 - 1.9.123 - fix(core)
 | 
					## 2024-09-27 - 1.9.123 - fix(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update dependencies and improve build configurations
 | 
					Update dependencies and improve build configurations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated several dependencies in package.json for better compatibility
 | 
					- Updated several dependencies in package.json for better compatibility
 | 
				
			||||||
@@ -232,88 +476,111 @@ Update dependencies and improve build configurations
 | 
				
			|||||||
- Provided initial structure for readme and readme hints
 | 
					- Provided initial structure for readme and readme hints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-24 - 1.9.122 - fix(mod_commit)
 | 
					## 2024-06-24 - 1.9.122 - fix(mod_commit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update package.json dependencies: @git.zone/tsdoc and @push.rocks/smartpromise to latest versions.
 | 
					Update package.json dependencies: @git.zone/tsdoc and @push.rocks/smartpromise to latest versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- - Updated @git.zone/tsdoc to ^1.3.12
 | 
					- - Updated @git.zone/tsdoc to ^1.3.12
 | 
				
			||||||
- - Updated @push.rocks/smartfile to ^11.0.21
 | 
					- - Updated @push.rocks/smartfile to ^11.0.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.121 - fix(mod_commit)
 | 
					## 2024-06-23 - 1.9.121 - fix(mod_commit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fix changelog template rendering by removing extra new line when no version details are provided.
 | 
					Fix changelog template rendering by removing extra new line when no version details are provided.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Update package.json dependencies: @git.zone/tsdoc and @push.rocks/smartpromise to latest versions.
 | 
					- Update package.json dependencies: @git.zone/tsdoc and @push.rocks/smartpromise to latest versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.120 - fix(mod_commit)
 | 
					## 2024-06-23 - 1.9.120 - fix(mod_commit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Handle edge case for empty version details in changelog formatting
 | 
					Handle edge case for empty version details in changelog formatting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Added check for the length of the recommendedNextVersionDetails array
 | 
					- Added check for the length of the recommendedNextVersionDetails array
 | 
				
			||||||
- Ensure no extra newline in changelog if there are no version details
 | 
					- Ensure no extra newline in changelog if there are no version details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.119 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.119 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update @git.zone/tsdoc to v1.3.8
 | 
					Update @git.zone/tsdoc to v1.3.8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated @git.zone/tsdoc from v1.3.7 to v1.3.8 in package.json
 | 
					- Updated @git.zone/tsdoc from v1.3.7 to v1.3.8 in package.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.118 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.118 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update @git.zone/tsdoc to version 1.3.7
 | 
					Update @git.zone/tsdoc to version 1.3.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Bump @git.zone/tsdoc from 1.3.6 to 1.3.7 in both package.json and pnpm-lock.yaml
 | 
					- Bump @git.zone/tsdoc from 1.3.6 to 1.3.7 in both package.json and pnpm-lock.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.117 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.117 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update @git.zone/tsdoc dependency to v1.3.6
 | 
					Update @git.zone/tsdoc dependency to v1.3.6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated @git.zone/tsdoc version from 1.3.5 to 1.3.6 in package.json
 | 
					- Updated @git.zone/tsdoc version from 1.3.5 to 1.3.6 in package.json
 | 
				
			||||||
- Updated pnpm-lock.yaml to reflect the new version of @git.zone/tsdoc
 | 
					- Updated pnpm-lock.yaml to reflect the new version of @git.zone/tsdoc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.116 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.116 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update @git.zone/tsdoc to version 1.3.5
 | 
					Update @git.zone/tsdoc to version 1.3.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Updated the @git.zone/tsdoc dependency in package.json and pnpm-lock.yaml from version 1.3.4 to 1.3.5
 | 
					- Updated the @git.zone/tsdoc dependency in package.json and pnpm-lock.yaml from version 1.3.4 to 1.3.5
 | 
				
			||||||
- Removed the outdated changelog.md file.
 | 
					- Removed the outdated changelog.md file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.114 - fix(format)
 | 
					## 2024-06-23 - 1.9.114 - fix(format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fixed formatting issues across multiple TypeScript files.
 | 
					Fixed formatting issues across multiple TypeScript files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.113 - fix(mod_commit)
 | 
					## 2024-06-23 - 1.9.113 - fix(mod_commit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Remove extra new lines in changelog.
 | 
					Remove extra new lines in changelog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.112 - fix(core)
 | 
					## 2024-06-23 - 1.9.112 - fix(core)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update changelog formatting and remove outdated entries.
 | 
					Update changelog formatting and remove outdated entries.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.111 - fix(changelog)
 | 
					## 2024-06-23 - 1.9.111 - fix(changelog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Remove outdated changelog entries and update formatting.
 | 
					Remove outdated changelog entries and update formatting.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.110 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.110 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update @git.zone/tsdoc to version 1.3.4.
 | 
					Update @git.zone/tsdoc to version 1.3.4.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.109 - fix(changelog)
 | 
					## 2024-06-23 - 1.9.109 - fix(changelog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Remove outdated entries and adjust formatting in changelog.
 | 
					Remove outdated entries and adjust formatting in changelog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.108 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.108 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Update @git.zone/tsdoc dependency to version 1.3.2.
 | 
					Update @git.zone/tsdoc dependency to version 1.3.2.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.107 - fix(changelog)
 | 
					## 2024-06-23 - 1.9.107 - fix(changelog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Remove placeholder entries and adjust formatting in changelog.
 | 
					Remove placeholder entries and adjust formatting in changelog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.106 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.106 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Updated @git.zone/tsdoc from version 1.3.0 to 1.3.1.
 | 
					Updated @git.zone/tsdoc from version 1.3.0 to 1.3.1.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.105 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.105 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Updated @git.zone/tsdoc dependency from 1.2.2 to 1.3.0 in package.json and pnpm-lock.yaml.
 | 
					Updated @git.zone/tsdoc dependency from 1.2.2 to 1.3.0 in package.json and pnpm-lock.yaml.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.104 - fix(changelog)
 | 
					## 2024-06-23 - 1.9.104 - fix(changelog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Remove placeholder entries and adjust formatting in changelog.
 | 
					Remove placeholder entries and adjust formatting in changelog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.103 - fix(changelog)
 | 
					## 2024-06-23 - 1.9.103 - fix(changelog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fix changelog to remove placeholder entries and adjust formatting.
 | 
					Fix changelog to remove placeholder entries and adjust formatting.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.102 - fix(logging)
 | 
					## 2024-06-23 - 1.9.102 - fix(logging)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Optimize logger instantiation and configuration.
 | 
					Optimize logger instantiation and configuration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.101 - fix(metadata)
 | 
					## 2024-06-23 - 1.9.101 - fix(metadata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Ensure accurate project metadata in package.json.
 | 
					Ensure accurate project metadata in package.json.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.100 - fix(dependencies)
 | 
					## 2024-06-23 - 1.9.100 - fix(dependencies)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Updated @git.zone/tsdoc dependency version to ^1.2.2 in package.json and pnpm-lock.yaml.
 | 
					Updated @git.zone/tsdoc dependency version to ^1.2.2 in package.json and pnpm-lock.yaml.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2024-06-23 - 1.9.99 - fix(mod_commit)
 | 
					## 2024-06-23 - 1.9.99 - fix(mod_commit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Fix variable reassignment issue in changelog writing step.
 | 
					Fix variable reassignment issue in changelog writing step.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										49
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								package.json
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@git.zone/cli",
 | 
					  "name": "@git.zone/cli",
 | 
				
			||||||
  "private": false,
 | 
					  "private": false,
 | 
				
			||||||
  "version": "1.16.2",
 | 
					  "version": "1.19.1",
 | 
				
			||||||
  "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.",
 | 
					  "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.",
 | 
				
			||||||
  "main": "dist_ts/index.ts",
 | 
					  "main": "dist_ts/index.ts",
 | 
				
			||||||
  "typings": "dist_ts/index.d.ts",
 | 
					  "typings": "dist_ts/index.d.ts",
 | 
				
			||||||
@@ -57,44 +57,45 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
 | 
					  "homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@git.zone/tsbuild": "^2.3.2",
 | 
					    "@git.zone/tsbuild": "^2.7.1",
 | 
				
			||||||
    "@git.zone/tsrun": "^1.3.3",
 | 
					    "@git.zone/tsrun": "^1.6.2",
 | 
				
			||||||
    "@git.zone/tstest": "^1.0.96",
 | 
					    "@git.zone/tstest": "^2.7.0",
 | 
				
			||||||
 | 
					    "@push.rocks/smartdelay": "^3.0.5",
 | 
				
			||||||
 | 
					    "@push.rocks/smartfile": "^11.2.7",
 | 
				
			||||||
 | 
					    "@push.rocks/smartinteract": "^2.0.16",
 | 
				
			||||||
 | 
					    "@push.rocks/smartnetwork": "^4.4.0",
 | 
				
			||||||
 | 
					    "@push.rocks/smartshell": "^3.3.0",
 | 
				
			||||||
    "@types/node": "^22.15.18"
 | 
					    "@types/node": "^22.15.18"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@git.zone/tsdoc": "^1.5.0",
 | 
					    "@git.zone/tsdoc": "^1.6.0",
 | 
				
			||||||
    "@git.zone/tspublish": "^1.9.1",
 | 
					    "@git.zone/tspublish": "^1.10.3",
 | 
				
			||||||
    "@push.rocks/commitinfo": "^1.0.12",
 | 
					    "@push.rocks/commitinfo": "^1.0.12",
 | 
				
			||||||
    "@push.rocks/early": "^4.0.4",
 | 
					    "@push.rocks/early": "^4.0.4",
 | 
				
			||||||
    "@push.rocks/gulp-function": "^3.0.7",
 | 
					    "@push.rocks/gulp-function": "^3.0.7",
 | 
				
			||||||
    "@push.rocks/lik": "^6.2.2",
 | 
					    "@push.rocks/lik": "^6.2.2",
 | 
				
			||||||
    "@push.rocks/npmextra": "^5.1.2",
 | 
					    "@push.rocks/npmextra": "^5.3.3",
 | 
				
			||||||
    "@push.rocks/projectinfo": "^5.0.2",
 | 
					    "@push.rocks/projectinfo": "^5.0.2",
 | 
				
			||||||
    "@push.rocks/smartchok": "^1.0.34",
 | 
					    "@push.rocks/smartchok": "^1.1.1",
 | 
				
			||||||
    "@push.rocks/smartcli": "^4.0.11",
 | 
					    "@push.rocks/smartcli": "^4.0.19",
 | 
				
			||||||
    "@push.rocks/smartdelay": "^3.0.5",
 | 
					 | 
				
			||||||
    "@push.rocks/smartdiff": "^1.0.3",
 | 
					    "@push.rocks/smartdiff": "^1.0.3",
 | 
				
			||||||
    "@push.rocks/smartfile": "^11.2.0",
 | 
					 | 
				
			||||||
    "@push.rocks/smartgulp": "^3.0.4",
 | 
					    "@push.rocks/smartgulp": "^3.0.4",
 | 
				
			||||||
    "@push.rocks/smartinteract": "^2.0.15",
 | 
					    "@push.rocks/smartjson": "^5.2.0",
 | 
				
			||||||
    "@push.rocks/smartjson": "^5.0.20",
 | 
					 | 
				
			||||||
    "@push.rocks/smartlegal": "^1.0.27",
 | 
					    "@push.rocks/smartlegal": "^1.0.27",
 | 
				
			||||||
    "@push.rocks/smartlog": "^3.0.9",
 | 
					    "@push.rocks/smartlog": "^3.1.10",
 | 
				
			||||||
    "@push.rocks/smartlog-destination-local": "^9.0.2",
 | 
					    "@push.rocks/smartlog-destination-local": "^9.0.2",
 | 
				
			||||||
    "@push.rocks/smartmustache": "^3.0.2",
 | 
					    "@push.rocks/smartmustache": "^3.0.2",
 | 
				
			||||||
    "@push.rocks/smartnpm": "^2.0.4",
 | 
					    "@push.rocks/smartnpm": "^2.0.6",
 | 
				
			||||||
    "@push.rocks/smartobject": "^1.0.12",
 | 
					    "@push.rocks/smartobject": "^1.0.12",
 | 
				
			||||||
    "@push.rocks/smartopen": "^2.0.0",
 | 
					    "@push.rocks/smartopen": "^2.0.0",
 | 
				
			||||||
    "@push.rocks/smartpath": "^5.0.18",
 | 
					    "@push.rocks/smartpath": "^6.0.0",
 | 
				
			||||||
    "@push.rocks/smartpromise": "^4.2.3",
 | 
					    "@push.rocks/smartpromise": "^4.2.3",
 | 
				
			||||||
    "@push.rocks/smartscaf": "^4.0.16",
 | 
					    "@push.rocks/smartscaf": "^4.0.19",
 | 
				
			||||||
    "@push.rocks/smartshell": "^3.2.3",
 | 
					 | 
				
			||||||
    "@push.rocks/smartstream": "^3.2.5",
 | 
					    "@push.rocks/smartstream": "^3.2.5",
 | 
				
			||||||
    "@push.rocks/smartunique": "^3.0.9",
 | 
					    "@push.rocks/smartunique": "^3.0.9",
 | 
				
			||||||
    "@push.rocks/smartupdate": "^2.0.6",
 | 
					    "@push.rocks/smartupdate": "^2.0.6",
 | 
				
			||||||
    "@types/through2": "^2.0.41",
 | 
					    "@types/through2": "^2.0.41",
 | 
				
			||||||
    "prettier": "^3.5.3",
 | 
					    "prettier": "^3.6.2",
 | 
				
			||||||
    "through2": "^4.0.2"
 | 
					    "through2": "^4.0.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "files": [
 | 
					  "files": [
 | 
				
			||||||
@@ -113,9 +114,13 @@
 | 
				
			|||||||
    "last 1 chrome versions"
 | 
					    "last 1 chrome versions"
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "pnpm": {
 | 
					  "pnpm": {
 | 
				
			||||||
    "overrides": {
 | 
					    "overrides": {},
 | 
				
			||||||
      "peek-readable": "5.3.1"
 | 
					    "onlyBuiltDependencies": [
 | 
				
			||||||
    }
 | 
					      "esbuild",
 | 
				
			||||||
 | 
					      "mongodb-memory-server",
 | 
				
			||||||
 | 
					      "puppeteer",
 | 
				
			||||||
 | 
					      "sharp"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
 | 
					  "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8725
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8725
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,10 +1,11 @@
 | 
				
			|||||||
# Gitzone CLI - Development Hints
 | 
					# Gitzone CLI - Development Hints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* the cli of the git.zone project.
 | 
					- the cli of the git.zone project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Project Overview
 | 
					## Project Overview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Gitzone CLI (`@git.zone/cli`) is a comprehensive toolbelt for streamlining local development cycles. It provides utilities for:
 | 
					Gitzone CLI (`@git.zone/cli`) is a comprehensive toolbelt for streamlining local development cycles. It provides utilities for:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Project initialization and templating (via smartscaf)
 | 
					- Project initialization and templating (via smartscaf)
 | 
				
			||||||
- Code formatting and standardization
 | 
					- Code formatting and standardization
 | 
				
			||||||
- Version control and commit management
 | 
					- Version control and commit management
 | 
				
			||||||
@@ -14,12 +15,14 @@ Gitzone CLI (`@git.zone/cli`) is a comprehensive toolbelt for streamlining local
 | 
				
			|||||||
## Architecture
 | 
					## Architecture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Core Structure
 | 
					### Core Structure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Main CLI entry: `cli.ts` / `cli.child.ts`
 | 
					- Main CLI entry: `cli.ts` / `cli.child.ts`
 | 
				
			||||||
- Modular architecture with separate modules in `ts/mod_*` directories
 | 
					- Modular architecture with separate modules in `ts/mod_*` directories
 | 
				
			||||||
- Each module handles specific functionality (format, commit, docker, etc.)
 | 
					- Each module handles specific functionality (format, commit, docker, etc.)
 | 
				
			||||||
- Extensive use of plugins pattern via `plugins.ts` files
 | 
					- Extensive use of plugins pattern via `plugins.ts` files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Configuration Management
 | 
					### Configuration Management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Uses `npmextra.json` for all tool configuration
 | 
					- Uses `npmextra.json` for all tool configuration
 | 
				
			||||||
- Configuration stored under `gitzone` key in npmextra
 | 
					- Configuration stored under `gitzone` key in npmextra
 | 
				
			||||||
- No separate `.gitzonerc` file - everything in npmextra.json
 | 
					- No separate `.gitzonerc` file - everything in npmextra.json
 | 
				
			||||||
@@ -30,6 +33,7 @@ Gitzone CLI (`@git.zone/cli`) is a comprehensive toolbelt for streamlining local
 | 
				
			|||||||
The format module is responsible for project standardization:
 | 
					The format module is responsible for project standardization:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Current Modules:
 | 
					#### Current Modules:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. **cleanup** - Removes obsolete files (yarn.lock, tslint.json, etc.)
 | 
					1. **cleanup** - Removes obsolete files (yarn.lock, tslint.json, etc.)
 | 
				
			||||||
2. **copy** - File copying with glob patterns (fully implemented)
 | 
					2. **copy** - File copying with glob patterns (fully implemented)
 | 
				
			||||||
3. **gitignore** - Creates/updates .gitignore from templates
 | 
					3. **gitignore** - Creates/updates .gitignore from templates
 | 
				
			||||||
@@ -42,6 +46,7 @@ The format module is responsible for project standardization:
 | 
				
			|||||||
10. **tsconfig** - Formats TypeScript configuration
 | 
					10. **tsconfig** - Formats TypeScript configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Execution Order (Dependency-Based):
 | 
					#### Execution Order (Dependency-Based):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Modules are now executed in parallel groups based on dependencies
 | 
					- Modules are now executed in parallel groups based on dependencies
 | 
				
			||||||
- Independent modules run concurrently for better performance
 | 
					- Independent modules run concurrently for better performance
 | 
				
			||||||
- Dependency analyzer ensures correct execution order
 | 
					- Dependency analyzer ensures correct execution order
 | 
				
			||||||
@@ -182,7 +187,7 @@ gitzone format --clean-backups
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## API Changes
 | 
					## API Changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- smartfile API updated to use fs.* and memory.* namespaces
 | 
					- smartfile API updated to use fs._ and memory._ namespaces
 | 
				
			||||||
- smartnpm requires instance creation: `new NpmRegistry()`
 | 
					- smartnpm requires instance creation: `new NpmRegistry()`
 | 
				
			||||||
- All file operations now use updated APIs
 | 
					- All file operations now use updated APIs
 | 
				
			||||||
- Type imports use `import type` for proper verbatim module syntax
 | 
					- Type imports use `import type` for proper verbatim module syntax
 | 
				
			||||||
							
								
								
									
										164
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								readme.md
									
									
									
									
									
								
							@@ -7,7 +7,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## 🎯 What is gitzone?
 | 
					## 🎯 What is gitzone?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gitzone is a powerful command-line interface that supercharges your development workflow with automated project management, intelligent code formatting, and seamless version control. Whether you're bootstrapping a new TypeScript project, maintaining code quality, or managing complex multi-repository setups, gitzone has got you covered.
 | 
					gitzone is a powerful command-line interface that supercharges your development workflow with automated project management, intelligent code formatting, seamless version control, and development service orchestration. Whether you're bootstrapping a new TypeScript project, maintaining code quality, managing complex multi-repository setups, or spinning up local development databases, gitzone has got you covered.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🏃♂️ Quick Start
 | 
					## 🏃♂️ Quick Start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,7 +23,7 @@ pnpm add -g @git.zone/cli
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Once installed, you can use either `gitzone` or the shorter `gzone` command from anywhere in your terminal.
 | 
					Once installed, you can use either `gitzone` or the shorter `gzone` command from anywhere in your terminal.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Your First Command
 | 
					### Your First Commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# Create a new TypeScript npm package
 | 
					# Create a new TypeScript npm package
 | 
				
			||||||
@@ -32,12 +32,66 @@ gitzone template npm
 | 
				
			|||||||
# Format your entire codebase
 | 
					# Format your entire codebase
 | 
				
			||||||
gitzone format
 | 
					gitzone format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Start local MongoDB and MinIO services
 | 
				
			||||||
 | 
					gitzone services start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Create a semantic commit
 | 
					# Create a semantic commit
 | 
				
			||||||
gitzone commit
 | 
					gitzone commit
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🛠️ Core Features
 | 
					## 🛠️ Core Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🐳 Development Services Management (NEW!)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Effortlessly manage local MongoDB and MinIO (S3-compatible) services for your development environment:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					gitzone services [command]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Available commands:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **`start [service]`** - Start services (mongo|s3|all)
 | 
				
			||||||
 | 
					- **`stop [service]`** - Stop services (mongo|s3|all)
 | 
				
			||||||
 | 
					- **`restart [service]`** - Restart services
 | 
				
			||||||
 | 
					- **`status`** - Show current service status
 | 
				
			||||||
 | 
					- **`config`** - Display configuration details
 | 
				
			||||||
 | 
					- **`compass`** - Get MongoDB Compass connection string with network IP
 | 
				
			||||||
 | 
					- **`logs [service] [lines]`** - View service logs
 | 
				
			||||||
 | 
					- **`remove`** - Remove containers (preserves data)
 | 
				
			||||||
 | 
					- **`clean`** - Remove containers AND data (⚠️ destructive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Key features:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- 🎲 **Smart port assignment** - Automatically assigns random ports (20000-30000) to avoid conflicts
 | 
				
			||||||
 | 
					- 📦 **Project isolation** - Each project gets its own containers with unique names
 | 
				
			||||||
 | 
					- 💾 **Data persistence** - Data stored in `.nogit/` directories survives container restarts
 | 
				
			||||||
 | 
					- 🔗 **MongoDB Compass support** - Instantly get connection strings for GUI access
 | 
				
			||||||
 | 
					- 🌐 **Network IP detection** - Automatically detects your local network IP for remote connections
 | 
				
			||||||
 | 
					- ⚙️ **Auto-configuration** - Creates `.nogit/env.json` with smart defaults
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Example workflow:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Start all services for your project
 | 
				
			||||||
 | 
					gitzone services start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Check what's running
 | 
				
			||||||
 | 
					gitzone services status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Get MongoDB Compass connection string
 | 
				
			||||||
 | 
					gitzone services compass
 | 
				
			||||||
 | 
					# Output: mongodb://defaultadmin:defaultpass@192.168.1.100:27018/myproject?authSource=admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# View MongoDB logs
 | 
				
			||||||
 | 
					gitzone services logs mongo 50
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Stop services when done
 | 
				
			||||||
 | 
					gitzone services stop
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The services are configured via `.nogit/env.json` which is automatically created with secure defaults and random ports for each project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 📦 Project Templates
 | 
					### 📦 Project Templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Instantly scaffold production-ready projects with best practices built-in:
 | 
					Instantly scaffold production-ready projects with best practices built-in:
 | 
				
			||||||
@@ -47,12 +101,14 @@ gitzone template [template-name]
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Available templates:**
 | 
					**Available templates:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **`npm`** - TypeScript npm package with testing, CI/CD, and full tooling
 | 
					- **`npm`** - TypeScript npm package with testing, CI/CD, and full tooling
 | 
				
			||||||
- **`service`** - Microservice architecture with Docker support
 | 
					- **`service`** - Microservice architecture with Docker support
 | 
				
			||||||
- **`website`** - Modern web application with LitElement and service workers
 | 
					- **`website`** - Modern web application with LitElement and service workers
 | 
				
			||||||
- **`wcc`** - Web Component Collection for reusable UI components
 | 
					- **`wcc`** - Web Component Collection for reusable UI components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Each template comes pre-configured with:
 | 
					Each template comes pre-configured with:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- ✅ TypeScript with modern configurations
 | 
					- ✅ TypeScript with modern configurations
 | 
				
			||||||
- ✅ Automated testing setup
 | 
					- ✅ Automated testing setup
 | 
				
			||||||
- ✅ CI/CD pipelines (GitLab/GitHub)
 | 
					- ✅ CI/CD pipelines (GitLab/GitHub)
 | 
				
			||||||
@@ -81,6 +137,7 @@ gitzone format --verbose
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Format features:**
 | 
					**Format features:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 🔄 **Smart caching** - Only processes changed files
 | 
					- 🔄 **Smart caching** - Only processes changed files
 | 
				
			||||||
- 🛡️ **Rollback support** - Undo formatting changes if needed
 | 
					- 🛡️ **Rollback support** - Undo formatting changes if needed
 | 
				
			||||||
- 📊 **Detailed reporting** - See exactly what changed
 | 
					- 📊 **Detailed reporting** - See exactly what changed
 | 
				
			||||||
@@ -88,6 +145,7 @@ gitzone format --verbose
 | 
				
			|||||||
- 🎯 **Module-specific formatting** - Target specific formatters
 | 
					- 🎯 **Module-specific formatting** - Target specific formatters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Rollback capabilities:**
 | 
					**Rollback capabilities:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# List all available backups
 | 
					# List all available backups
 | 
				
			||||||
gitzone format --list-backups
 | 
					gitzone format --list-backups
 | 
				
			||||||
@@ -103,6 +161,7 @@ gitzone format --clean-backups
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Formatters included:**
 | 
					**Formatters included:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Prettier** - JavaScript/TypeScript code formatting
 | 
					- **Prettier** - JavaScript/TypeScript code formatting
 | 
				
			||||||
- **License** - Ensure proper licensing
 | 
					- **License** - Ensure proper licensing
 | 
				
			||||||
- **Package.json** - Standardize package configurations
 | 
					- **Package.json** - Standardize package configurations
 | 
				
			||||||
@@ -121,6 +180,7 @@ gitzone commit
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Features:
 | 
					Features:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- 📝 Interactive commit message builder
 | 
					- 📝 Interactive commit message builder
 | 
				
			||||||
- 🏷️ Automatic version bumping (major/minor/patch)
 | 
					- 🏷️ Automatic version bumping (major/minor/patch)
 | 
				
			||||||
- 📜 Changelog generation
 | 
					- 📜 Changelog generation
 | 
				
			||||||
@@ -128,6 +188,7 @@ Features:
 | 
				
			|||||||
- 🎯 Conventional commit compliance
 | 
					- 🎯 Conventional commit compliance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The commit wizard guides you through:
 | 
					The commit wizard guides you through:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. **Type selection** (feat/fix/docs/style/refactor/perf/test/chore)
 | 
					1. **Type selection** (feat/fix/docs/style/refactor/perf/test/chore)
 | 
				
			||||||
2. **Scope definition** (component/module affected)
 | 
					2. **Scope definition** (component/module affected)
 | 
				
			||||||
3. **Description crafting**
 | 
					3. **Description crafting**
 | 
				
			||||||
@@ -153,6 +214,7 @@ gitzone meta remove [name]
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Perfect for:
 | 
					Perfect for:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Monorepo management
 | 
					- Monorepo management
 | 
				
			||||||
- Multi-package projects
 | 
					- Multi-package projects
 | 
				
			||||||
- Coordinated deployments
 | 
					- Coordinated deployments
 | 
				
			||||||
@@ -168,6 +230,7 @@ gitzone docker prune
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This command removes:
 | 
					This command removes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Stopped containers
 | 
					- Stopped containers
 | 
				
			||||||
- Unused images
 | 
					- Unused images
 | 
				
			||||||
- Dangling volumes
 | 
					- Dangling volumes
 | 
				
			||||||
@@ -196,6 +259,7 @@ gitzone deprecate
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Interactive wizard for:
 | 
					Interactive wizard for:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Setting deprecation notices
 | 
					- Setting deprecation notices
 | 
				
			||||||
- Guiding users to replacements
 | 
					- Guiding users to replacements
 | 
				
			||||||
- Updating registry metadata
 | 
					- Updating registry metadata
 | 
				
			||||||
@@ -210,6 +274,7 @@ gitzone start
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Automatically:
 | 
					Automatically:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Checks out master branch
 | 
					- Checks out master branch
 | 
				
			||||||
- Pulls latest changes
 | 
					- Pulls latest changes
 | 
				
			||||||
- Installs dependencies
 | 
					- Installs dependencies
 | 
				
			||||||
@@ -266,44 +331,58 @@ Customize gitzone behavior through `npmextra.json`:
 | 
				
			|||||||
## 🏆 Best Practices
 | 
					## 🏆 Best Practices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### For New Projects
 | 
					### For New Projects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Start with a template: `gitzone template npm`
 | 
					1. Start with a template: `gitzone template npm`
 | 
				
			||||||
2. Customize the generated structure
 | 
					2. Set up local services: `gitzone services start`
 | 
				
			||||||
3. Run initial format: `gitzone format`
 | 
					3. Customize the generated structure
 | 
				
			||||||
4. Set up CI/CD: `gitzone open ci`
 | 
					4. Run initial format: `gitzone format`
 | 
				
			||||||
 | 
					5. Set up CI/CD: `gitzone open ci`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### For Existing Projects
 | 
					### For Existing Projects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Initialize: `gitzone start`
 | 
					1. Initialize: `gitzone start`
 | 
				
			||||||
2. Format codebase: `gitzone format --dry-run` (preview first!)
 | 
					2. Format codebase: `gitzone format --dry-run` (preview first!)
 | 
				
			||||||
3. Apply formatting: `gitzone format --yes`
 | 
					3. Apply formatting: `gitzone format --yes`
 | 
				
			||||||
4. Commit changes: `gitzone commit`
 | 
					4. Set up services: `gitzone services start`
 | 
				
			||||||
 | 
					5. Commit changes: `gitzone commit`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### For Teams
 | 
					### For Teams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Document format preferences in `npmextra.json`
 | 
					1. Document format preferences in `npmextra.json`
 | 
				
			||||||
2. Use `--save-plan` for reviewable format changes
 | 
					2. Share `.nogit/env.json` template for consistent service setup
 | 
				
			||||||
3. Enable rollback for safety
 | 
					3. Use `--save-plan` for reviewable format changes
 | 
				
			||||||
4. Standardize commit conventions
 | 
					4. Enable rollback for safety
 | 
				
			||||||
 | 
					5. Standardize commit conventions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🎯 Common Workflows
 | 
					## 🎯 Common Workflows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Clean Development Cycle
 | 
					### Full-Stack Development Cycle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# 1. Start fresh
 | 
					# 1. Start fresh
 | 
				
			||||||
gitzone start
 | 
					gitzone start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 2. Make changes
 | 
					# 2. Spin up databases and services
 | 
				
			||||||
 | 
					gitzone services start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 3. Make changes
 | 
				
			||||||
# ... your development work ...
 | 
					# ... your development work ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 3. Format code
 | 
					# 4. Check service logs if needed
 | 
				
			||||||
 | 
					gitzone services logs mongo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 5. Format code
 | 
				
			||||||
gitzone format
 | 
					gitzone format
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 4. Commit with semantic versioning
 | 
					# 6. Commit with semantic versioning
 | 
				
			||||||
gitzone commit
 | 
					gitzone commit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 5. Deploy (if CI/CD configured)
 | 
					# 7. Stop services when done
 | 
				
			||||||
# Automatic via git push
 | 
					gitzone services stop
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Multi-Repository Management
 | 
					### Multi-Repository Management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# 1. Set up meta repository
 | 
					# 1. Set up meta repository
 | 
				
			||||||
gitzone meta init
 | 
					gitzone meta init
 | 
				
			||||||
@@ -318,6 +397,7 @@ gitzone meta update
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Safe Formatting with Rollback
 | 
					### Safe Formatting with Rollback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
# 1. Preview changes
 | 
					# 1. Preview changes
 | 
				
			||||||
gitzone format --dry-run
 | 
					gitzone format --dry-run
 | 
				
			||||||
@@ -332,20 +412,45 @@ gitzone format --from-plan format-changes.json
 | 
				
			|||||||
gitzone format --rollback
 | 
					gitzone format --rollback
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Database-Driven Development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# 1. Start MongoDB and MinIO
 | 
				
			||||||
 | 
					gitzone services start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 2. Get connection string for your app
 | 
				
			||||||
 | 
					gitzone services config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 3. Connect with MongoDB Compass
 | 
				
			||||||
 | 
					gitzone services compass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 4. Monitor services
 | 
				
			||||||
 | 
					gitzone services status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 5. Clean everything when done
 | 
				
			||||||
 | 
					gitzone services clean  # ⚠️ Warning: deletes data
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🔌 Integrations
 | 
					## 🔌 Integrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### CI/CD Platforms
 | 
					### CI/CD Platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **GitLab CI** - Full pipeline support with templates
 | 
					- **GitLab CI** - Full pipeline support with templates
 | 
				
			||||||
- **GitHub Actions** - Automated workflows
 | 
					- **GitHub Actions** - Automated workflows
 | 
				
			||||||
- **Docker** - Container-based deployments
 | 
					- **Docker** - Container-based deployments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Development Tools
 | 
					### Development Tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **TypeScript** - First-class support
 | 
					- **TypeScript** - First-class support
 | 
				
			||||||
- **Prettier** - Code formatting
 | 
					- **Prettier** - Code formatting
 | 
				
			||||||
- **ESLint** - Linting (via format modules)
 | 
					- **ESLint** - Linting (via format modules)
 | 
				
			||||||
- **npm/pnpm** - Package management
 | 
					- **npm/pnpm** - Package management
 | 
				
			||||||
 | 
					- **MongoDB** - Local database service
 | 
				
			||||||
 | 
					- **MinIO** - S3-compatible object storage
 | 
				
			||||||
 | 
					- **MongoDB Compass** - Database GUI integration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Version Control
 | 
					### Version Control
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Git** - Deep integration
 | 
					- **Git** - Deep integration
 | 
				
			||||||
- **Semantic Versioning** - Automatic version bumping
 | 
					- **Semantic Versioning** - Automatic version bumping
 | 
				
			||||||
- **Conventional Commits** - Standardized commit messages
 | 
					- **Conventional Commits** - Standardized commit messages
 | 
				
			||||||
@@ -357,34 +462,63 @@ gitzone format --rollback
 | 
				
			|||||||
3. **Leverage templates**: Start projects right with proven structures
 | 
					3. **Leverage templates**: Start projects right with proven structures
 | 
				
			||||||
4. **Enable caching**: Dramatically speeds up formatting operations
 | 
					4. **Enable caching**: Dramatically speeds up formatting operations
 | 
				
			||||||
5. **Save format plans**: Review changes before applying in production
 | 
					5. **Save format plans**: Review changes before applying in production
 | 
				
			||||||
 | 
					6. **Port management**: Let services auto-assign ports to avoid conflicts
 | 
				
			||||||
 | 
					7. **Use MongoDB Compass**: `gitzone services compass` for visual DB management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🐛 Troubleshooting
 | 
					## 🐛 Troubleshooting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Format Command Shows "Cancelled"
 | 
					### Format Command Shows "Cancelled"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If the format command shows cancelled even after confirming:
 | 
					If the format command shows cancelled even after confirming:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Check your `npmextra.json` configuration
 | 
					- Check your `npmextra.json` configuration
 | 
				
			||||||
- Try with `--yes` flag to skip confirmation
 | 
					- Try with `--yes` flag to skip confirmation
 | 
				
			||||||
- Use `--verbose` for detailed output
 | 
					- Use `--verbose` for detailed output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Docker Commands Fail
 | 
					### Docker Commands Fail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Ensure Docker daemon is running:
 | 
					Ensure Docker daemon is running:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
docker info
 | 
					docker info
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Services Won't Start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Check for port conflicts:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Services auto-assign ports, but you can check the config
 | 
				
			||||||
 | 
					cat .nogit/env.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Verify Docker is running
 | 
				
			||||||
 | 
					docker ps
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Template Creation Issues
 | 
					### Template Creation Issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Verify npm/pnpm is properly configured:
 | 
					Verify npm/pnpm is properly configured:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
npm config get registry
 | 
					npm config get registry
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### MongoDB Connection Issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Ensure services are running: `gitzone services status`
 | 
				
			||||||
 | 
					- Check firewall settings for the assigned ports
 | 
				
			||||||
 | 
					- Use `gitzone services compass` for the correct connection string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 📈 Performance
 | 
					## 📈 Performance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gitzone is optimized for speed:
 | 
					gitzone is optimized for speed:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Parallel processing** for format operations
 | 
					- **Parallel processing** for format operations
 | 
				
			||||||
- **Smart caching** to avoid redundant work
 | 
					- **Smart caching** to avoid redundant work
 | 
				
			||||||
- **Incremental updates** for meta repositories
 | 
					- **Incremental updates** for meta repositories
 | 
				
			||||||
- **Minimal dependencies** for fast installation
 | 
					- **Minimal dependencies** for fast installation
 | 
				
			||||||
 | 
					- **Isolated services** prevent resource conflicts
 | 
				
			||||||
 | 
					- **Auto port assignment** eliminates manual configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## License and Legal Information
 | 
					## License and Legal Information
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										257
									
								
								readme.plan.md
									
									
									
									
									
								
							
							
						
						
									
										257
									
								
								readme.plan.md
									
									
									
									
									
								
							@@ -1,170 +1,121 @@
 | 
				
			|||||||
# Gitzone Format Module Improvement Plan
 | 
					# GitZone Services Command Implementation Plan
 | 
				
			||||||
 | 
					 | 
				
			||||||
Please reread /home/philkunz/.claude/CLAUDE.md before proceeding with any implementation.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Overview
 | 
					## Overview
 | 
				
			||||||
This plan outlines improvements for the gitzone format module to enhance its functionality, reliability, and maintainability.
 | 
					Implement the `gitzone services` command to manage MongoDB and MinIO containers for development projects.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Phase 1: Core Improvements (High Priority) - COMPLETED ✅
 | 
					## Tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 1. Enhanced Error Handling & Recovery ✅
 | 
					### Module Structure Setup
 | 
				
			||||||
- [x] Implement rollback mechanism for failed format operations
 | 
					- [x] Create `ts/mod_services/` directory
 | 
				
			||||||
- [x] Add detailed error messages with recovery suggestions
 | 
					- [x] Create `mod.plugins.ts` with required imports
 | 
				
			||||||
- [x] Create a `--dry-run` flag to preview changes before applying
 | 
					- [x] Create `helpers.ts` with utility functions
 | 
				
			||||||
- [x] Add transaction-like behavior: all-or-nothing formatting
 | 
					- [x] Create `classes.serviceconfiguration.ts` for config handling
 | 
				
			||||||
- [x] Implement plan → action workflow as default behavior
 | 
					- [x] Create `classes.dockercontainer.ts` for Docker operations
 | 
				
			||||||
 | 
					- [x] Create `classes.servicemanager.ts` for service management
 | 
				
			||||||
 | 
					- [x] Create `index.ts` with main command logic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 2. Complete Missing Functionality ✅
 | 
					### Core Functionality
 | 
				
			||||||
- [x] Implement the `ensureDependency` function in format.packagejson.ts
 | 
					- [x] Implement ServiceConfiguration class
 | 
				
			||||||
- [x] Develop the copy module for file pattern-based copying
 | 
					  - [x] Load/create `.nogit/env.json` configuration
 | 
				
			||||||
- [x] Add dependency version constraint management
 | 
					  - [x] Generate random available ports (20000-30000 range)
 | 
				
			||||||
- [x] Support workspace/monorepo configurations (via configuration)
 | 
					  - [x] Preserve existing custom values
 | 
				
			||||||
 | 
					  - [x] Provide default values for missing fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 3. Configuration & Flexibility ✅
 | 
					- [x] Implement DockerContainer class
 | 
				
			||||||
- [x] Extend npmextra.json gitzone configuration section
 | 
					  - [x] Check container status
 | 
				
			||||||
- [x] Allow custom license exclusion/inclusion lists
 | 
					  - [x] Start/stop/restart containers
 | 
				
			||||||
- [x] Make format steps configurable (skip/include specific modules)
 | 
					  - [x] Execute Docker commands
 | 
				
			||||||
- [x] Support custom template directories (via configuration)
 | 
					  - [x] Handle container logs
 | 
				
			||||||
- [x] Add format profiles for different project types
 | 
					  - [x] Manage volumes and port bindings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 4. Architecture Changes ✅
 | 
					- [x] Implement ServiceManager class
 | 
				
			||||||
- [x] Introduce a `FormatContext` class to manage state across modules
 | 
					  - [x] Manage MongoDB containers
 | 
				
			||||||
- [x] Create abstract `BaseFormatter` class for consistent module structure
 | 
					  - [x] Manage MinIO containers
 | 
				
			||||||
- [x] Implement event system for inter-module communication (via context)
 | 
					  - [x] Handle container lifecycle
 | 
				
			||||||
- [x] Add validation layer before format execution
 | 
					  - [x] Generate project-specific container names
 | 
				
			||||||
- [x] Implement `FormatPlanner` class for plan → action workflow
 | 
					  - [x] Manage data directories in `.nogit/`
 | 
				
			||||||
 | 
					  - [x] Generate MongoDB Compass connection strings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Phase 2: Performance & Reporting (Medium Priority) - COMPLETED ✅
 | 
					### Commands Implementation
 | 
				
			||||||
 | 
					- [x] `start` command - Start services (mongo|s3|all)
 | 
				
			||||||
 | 
					- [x] `stop` command - Stop services (mongo|s3|all)
 | 
				
			||||||
 | 
					- [x] `restart` command - Restart services (mongo|s3|all)
 | 
				
			||||||
 | 
					- [x] `status` command - Show service status
 | 
				
			||||||
 | 
					- [x] `config` command - Show current configuration
 | 
				
			||||||
 | 
					- [x] `compass` command - Show MongoDB Compass connection string
 | 
				
			||||||
 | 
					- [x] `logs` command - Show service logs with line count
 | 
				
			||||||
 | 
					- [x] `remove` command - Remove containers (preserve data)
 | 
				
			||||||
 | 
					- [x] `clean` command - Remove containers and data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 5. Performance Optimizations ✅
 | 
					### Integration
 | 
				
			||||||
- [x] Implement parallel execution for independent format modules
 | 
					- [x] Add `@push.rocks/smartshell` to main plugins.ts
 | 
				
			||||||
- [x] Add file change detection to skip unchanged files
 | 
					- [x] Add `@push.rocks/smartnetwork` to main plugins.ts
 | 
				
			||||||
- [x] Create format cache to track last formatted state
 | 
					- [x] Add `@push.rocks/smartinteraction` to main plugins.ts
 | 
				
			||||||
- [x] Optimize Prettier runs by batching files
 | 
					- [x] Register services command in `gitzone.cli.ts`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 6. Enhanced Reporting & Visibility ✅
 | 
					### Features
 | 
				
			||||||
- [x] Generate comprehensive format report showing all changes
 | 
					- [x] Auto-configuration with smart defaults
 | 
				
			||||||
- [x] Add diff view for file modifications
 | 
					- [x] Random port assignment to avoid conflicts
 | 
				
			||||||
- [x] Create verbose logging option
 | 
					- [x] Project isolation with unique container names
 | 
				
			||||||
- [x] Add format statistics (files changed, time taken, etc.)
 | 
					- [x] Data persistence in `.nogit/` directories
 | 
				
			||||||
 | 
					- [x] Status display (running/stopped/not installed)
 | 
				
			||||||
 | 
					- [x] Interactive confirmations for destructive operations
 | 
				
			||||||
 | 
					- [x] Colored console output
 | 
				
			||||||
 | 
					- [x] MinIO bucket auto-creation
 | 
				
			||||||
 | 
					- [x] MongoDB Compass connection string with network IP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Phase 3: Advanced Features (Lower Priority) - PARTIALLY COMPLETED
 | 
					### Testing
 | 
				
			||||||
 | 
					- [ ] Test service start/stop operations
 | 
				
			||||||
 | 
					- [ ] Test configuration creation and updates
 | 
				
			||||||
 | 
					- [ ] Test port collision handling
 | 
				
			||||||
 | 
					- [ ] Test data persistence
 | 
				
			||||||
 | 
					- [ ] Test MongoDB Compass connection string generation
 | 
				
			||||||
 | 
					- [ ] Test all command variations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 7. Better Integration & Extensibility ⏳
 | 
					## Configuration Format
 | 
				
			||||||
- [ ] Create plugin system for custom format modules
 | 
					```json
 | 
				
			||||||
- [ ] Add hooks for pre/post format operations
 | 
					{
 | 
				
			||||||
- [ ] Support custom validation rules
 | 
					  "PROJECT_NAME": "derived-from-package-name",
 | 
				
			||||||
- [ ] Integrate with git hooks for pre-commit formatting
 | 
					  "MONGODB_HOST": "localhost",
 | 
				
			||||||
 | 
					  "MONGODB_NAME": "project-name",
 | 
				
			||||||
 | 
					  "MONGODB_PORT": "random-port",
 | 
				
			||||||
 | 
					  "MONGODB_USER": "defaultadmin",
 | 
				
			||||||
 | 
					  "MONGODB_PASS": "defaultpass",
 | 
				
			||||||
 | 
					  "S3_HOST": "localhost",
 | 
				
			||||||
 | 
					  "S3_PORT": "random-port",
 | 
				
			||||||
 | 
					  "S3_CONSOLE_PORT": "s3-port+1",
 | 
				
			||||||
 | 
					  "S3_USER": "defaultadmin",
 | 
				
			||||||
 | 
					  "S3_PASS": "defaultpass",
 | 
				
			||||||
 | 
					  "S3_BUCKET": "project-name-documents"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 8. Improved Template Integration ⏳
 | 
					## Command Examples
 | 
				
			||||||
- [ ] Better error handling when smartscaf operations fail
 | 
					```bash
 | 
				
			||||||
- [ ] Add pre/post template hooks for custom processing
 | 
					gitzone services start          # Start all services
 | 
				
			||||||
- [ ] Validate template results before proceeding with format
 | 
					gitzone services start mongo    # Start only MongoDB
 | 
				
			||||||
- [ ] Support skipping template updates via configuration
 | 
					gitzone services stop           # Stop all services
 | 
				
			||||||
 | 
					gitzone services status         # Check service status
 | 
				
			||||||
 | 
					gitzone services config         # Show configuration
 | 
				
			||||||
 | 
					gitzone services compass        # Show MongoDB Compass connection string
 | 
				
			||||||
 | 
					gitzone services logs mongo 50  # Show last 50 lines of MongoDB logs
 | 
				
			||||||
 | 
					gitzone services remove         # Remove containers (preserve data)
 | 
				
			||||||
 | 
					gitzone services clean          # Remove containers and data
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 9. Enhanced License Management ⏳
 | 
					## Progress Notes
 | 
				
			||||||
- [ ] Make license checking configurable (partial)
 | 
					Implementation started: 2025-08-14
 | 
				
			||||||
- [ ] Add license compatibility matrix
 | 
					Implementation completed: 2025-08-14
 | 
				
			||||||
- [x] Support license exceptions for specific packages
 | 
					 | 
				
			||||||
- [ ] Generate license report for compliance
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 10. Better Package.json Management ⏳
 | 
					## Summary
 | 
				
			||||||
- [ ] Smart dependency sorting and grouping
 | 
					Successfully implemented the `gitzone services` command in TypeScript, providing a complete replacement for the `services.sh` shell script. The implementation includes:
 | 
				
			||||||
- [ ] Automated script generation based on project type
 | 
					 | 
				
			||||||
- [ ] Support for pnpm workspace configurations
 | 
					 | 
				
			||||||
- [ ] Validation of package.json schema
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 11. Quality of Life Improvements ⏳
 | 
					1. **Complete Docker service management** for MongoDB and MinIO containers
 | 
				
			||||||
- [ ] Interactive mode for format configuration
 | 
					2. **Smart configuration management** with automatic port assignment and conflict avoidance
 | 
				
			||||||
- [ ] Undo/redo capability for format operations
 | 
					3. **MongoDB Compass support** with network IP detection for remote connections
 | 
				
			||||||
- [ ] Format presets for common scenarios
 | 
					4. **Project isolation** using project-specific container names
 | 
				
			||||||
- [x] Better progress indicators and user feedback
 | 
					5. **Data persistence** in `.nogit/` directories
 | 
				
			||||||
 | 
					6. **Interactive confirmations** for destructive operations
 | 
				
			||||||
 | 
					7. **Comprehensive command set** including start, stop, restart, status, config, compass, logs, remove, and clean commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Implementation Status
 | 
					The module is fully integrated into the gitzone CLI and ready for testing.
 | 
				
			||||||
 | 
					 | 
				
			||||||
### ✅ Completed Features
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. **Rollback Mechanism**
 | 
					 | 
				
			||||||
   - Full backup/restore functionality
 | 
					 | 
				
			||||||
   - Manifest tracking and integrity checks
 | 
					 | 
				
			||||||
   - CLI commands for rollback operations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
2. **Plan → Action Workflow**
 | 
					 | 
				
			||||||
   - Two-phase approach (analyze then execute)
 | 
					 | 
				
			||||||
   - Interactive confirmation
 | 
					 | 
				
			||||||
   - Dry-run support
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
3. **Configuration System**
 | 
					 | 
				
			||||||
   - Comprehensive npmextra.json support
 | 
					 | 
				
			||||||
   - Module control (skip/only/order)
 | 
					 | 
				
			||||||
   - Cache configuration
 | 
					 | 
				
			||||||
   - Parallel execution settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
4. **Performance Improvements**
 | 
					 | 
				
			||||||
   - Parallel execution by dependency analysis
 | 
					 | 
				
			||||||
   - File change caching
 | 
					 | 
				
			||||||
   - Prettier batching
 | 
					 | 
				
			||||||
   - Execution time tracking
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
5. **Reporting & Statistics**
 | 
					 | 
				
			||||||
   - Detailed diff views
 | 
					 | 
				
			||||||
   - Execution statistics
 | 
					 | 
				
			||||||
   - Verbose logging mode
 | 
					 | 
				
			||||||
   - Save reports to file
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
6. **Architecture Improvements**
 | 
					 | 
				
			||||||
   - BaseFormatter abstract class
 | 
					 | 
				
			||||||
   - FormatContext for state management
 | 
					 | 
				
			||||||
   - DependencyAnalyzer for parallel execution
 | 
					 | 
				
			||||||
   - Type-safe interfaces
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 🚧 Partially Completed
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. **License Management**
 | 
					 | 
				
			||||||
   - Basic configuration support
 | 
					 | 
				
			||||||
   - Exception handling for specific packages
 | 
					 | 
				
			||||||
   - Need: compatibility matrix, compliance reports
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
2. **Package.json Management**
 | 
					 | 
				
			||||||
   - Basic ensureDependency implementation
 | 
					 | 
				
			||||||
   - Need: smart sorting, script generation, validation
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### ⏳ Not Started
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. **Plugin System**
 | 
					 | 
				
			||||||
   - Need to design plugin API
 | 
					 | 
				
			||||||
   - Hook system for pre/post operations
 | 
					 | 
				
			||||||
   - Custom validation rules
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
2. **Git Integration**
 | 
					 | 
				
			||||||
   - Pre-commit hooks
 | 
					 | 
				
			||||||
   - Automatic formatting on commit
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
3. **Advanced UI**
 | 
					 | 
				
			||||||
   - Interactive configuration mode
 | 
					 | 
				
			||||||
   - Undo/redo capability
 | 
					 | 
				
			||||||
   - Format presets
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Technical Achievements
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. **Type Safety**: All new code uses TypeScript interfaces and types
 | 
					 | 
				
			||||||
2. **Error Handling**: Comprehensive try-catch blocks with rollback
 | 
					 | 
				
			||||||
3. **API Compatibility**: Updated to use latest smartfile/smartnpm APIs
 | 
					 | 
				
			||||||
4. **Testing**: Ready for comprehensive test suite
 | 
					 | 
				
			||||||
5. **Performance**: Significant improvements through caching and parallelization
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Next Steps
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. Write comprehensive tests for all new functionality
 | 
					 | 
				
			||||||
2. Create user documentation for new features
 | 
					 | 
				
			||||||
3. Consider plugin API design for extensibility
 | 
					 | 
				
			||||||
4. Implement remaining Phase 3 features based on user feedback
 | 
					 | 
				
			||||||
5. Performance benchmarking and optimization
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Success Metrics Achieved
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- ✅ Reduced error rates through rollback mechanism
 | 
					 | 
				
			||||||
- ✅ Faster execution through parallel processing and caching
 | 
					 | 
				
			||||||
- ✅ Enhanced user control through configuration
 | 
					 | 
				
			||||||
- ✅ Better visibility through reporting and statistics
 | 
					 | 
				
			||||||
- ✅ Improved maintainability through better architecture
 | 
					 | 
				
			||||||
							
								
								
									
										1
									
								
								test
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								test
									
									
									
									
									
										Submodule
									
								
							 Submodule test added at 0b89443584
									
								
							@@ -3,6 +3,6 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const commitinfo = {
 | 
					export const commitinfo = {
 | 
				
			||||||
  name: '@git.zone/cli',
 | 
					  name: '@git.zone/cli',
 | 
				
			||||||
  version: '1.16.1',
 | 
					  version: '1.19.1',
 | 
				
			||||||
  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.'
 | 
					  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.'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,9 @@ export class GitzoneConfig {
 | 
				
			|||||||
  public async readConfigFromCwd() {
 | 
					  public async readConfigFromCwd() {
 | 
				
			||||||
    const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
 | 
					    const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
 | 
				
			||||||
    this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {});
 | 
					    this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {});
 | 
				
			||||||
    this.data.npmciOptions = npmextraInstance.dataFor<IGitzoneConfigData['npmciOptions']>('npmci', {
 | 
					    this.data.npmciOptions = npmextraInstance.dataFor<
 | 
				
			||||||
 | 
					      IGitzoneConfigData['npmciOptions']
 | 
				
			||||||
 | 
					    >('npmci', {
 | 
				
			||||||
      npmAccessLevel: 'public',
 | 
					      npmAccessLevel: 'public',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,7 +89,7 @@ export let run = async () => {
 | 
				
			|||||||
      detailed: argvArg.detailed,
 | 
					      detailed: argvArg.detailed,
 | 
				
			||||||
      interactive: argvArg.interactive !== false,
 | 
					      interactive: argvArg.interactive !== false,
 | 
				
			||||||
      parallel: argvArg.parallel !== false,
 | 
					      parallel: argvArg.parallel !== false,
 | 
				
			||||||
      verbose: argvArg.verbose
 | 
					      verbose: argvArg.verbose,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -131,6 +131,14 @@ export let run = async () => {
 | 
				
			|||||||
    modHelpers.run(argvArg);
 | 
					    modHelpers.run(argvArg);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * manage development services (MongoDB, S3/MinIO)
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  gitzoneSmartcli.addCommand('services').subscribe(async (argvArg) => {
 | 
				
			||||||
 | 
					    const modServices = await import('./mod_services/index.js');
 | 
				
			||||||
 | 
					    await modServices.run(argvArg);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // start parsing of the cli
 | 
					  // start parsing of the cli
 | 
				
			||||||
  gitzoneSmartcli.startParse();
 | 
					  gitzoneSmartcli.startParse();
 | 
				
			||||||
  return await done.promise;
 | 
					  return await done.promise;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,8 @@ import * as plugins from './plugins.js';
 | 
				
			|||||||
export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
 | 
					export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add console destination
 | 
					// Add console destination
 | 
				
			||||||
const consoleDestination = new plugins.smartlogDestinationLocal.DestinationLocal();
 | 
					const consoleDestination =
 | 
				
			||||||
 | 
					  new plugins.smartlogDestinationLocal.DestinationLocal();
 | 
				
			||||||
logger.addLogDestination(consoleDestination);
 | 
					logger.addLogDestination(consoleDestination);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Verbose logging helper
 | 
					// Verbose logging helper
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,8 @@
 | 
				
			|||||||
import * as plugins from './mod.plugins.js';
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
import * as paths from '../paths.js';
 | 
					import * as paths from '../paths.js';
 | 
				
			||||||
import { logger } from '../gitzone.logging.js';
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					import * as helpers from './mod.helpers.js';
 | 
				
			||||||
 | 
					import * as ui from './mod.ui.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const run = async (argvArg: any) => {
 | 
					export const run = async (argvArg: any) => {
 | 
				
			||||||
  if (argvArg.format) {
 | 
					  if (argvArg.format) {
 | 
				
			||||||
@@ -10,20 +12,21 @@ export const run = async (argvArg: any) => {
 | 
				
			|||||||
    await formatMod.run();
 | 
					    await formatMod.run();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ui.printHeader('🔍 Analyzing repository changes...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  logger.log('info', `gathering facts...`);
 | 
					 | 
				
			||||||
  const aidoc = new plugins.tsdoc.AiDoc();
 | 
					  const aidoc = new plugins.tsdoc.AiDoc();
 | 
				
			||||||
  await aidoc.start();
 | 
					  await aidoc.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd);
 | 
					  const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  logger.log('info', `---------
 | 
					  await aidoc.stop();
 | 
				
			||||||
    Next recommended commit would be:
 | 
					
 | 
				
			||||||
    ===========
 | 
					  ui.printRecommendation({
 | 
				
			||||||
    -> ${nextCommitObject.recommendedNextVersion}:
 | 
					    recommendedNextVersion: nextCommitObject.recommendedNextVersion,
 | 
				
			||||||
    -> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage}
 | 
					    recommendedNextVersionLevel: nextCommitObject.recommendedNextVersionLevel,
 | 
				
			||||||
    ===========
 | 
					    recommendedNextVersionScope: nextCommitObject.recommendedNextVersionScope,
 | 
				
			||||||
  `);
 | 
					    recommendedNextVersionMessage: nextCommitObject.recommendedNextVersionMessage,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
  const commitInteract = new plugins.smartinteract.SmartInteract();
 | 
					  const commitInteract = new plugins.smartinteract.SmartInteract();
 | 
				
			||||||
  commitInteract.addQuestions([
 | 
					  commitInteract.addQuestions([
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -65,39 +68,109 @@ export const run = async (argvArg: any) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  })();
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  logger.log('info', `OK! Creating commit with message '${commitString}'`);
 | 
					  ui.printHeader('✨ Creating Semantic Commit');
 | 
				
			||||||
 | 
					  ui.printCommitMessage(commitString);
 | 
				
			||||||
  const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
					  const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
				
			||||||
    executor: 'bash',
 | 
					    executor: 'bash',
 | 
				
			||||||
    sourceFilePaths: [],
 | 
					    sourceFilePaths: [],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  logger.log('info', `Baking commitinfo into code ...`);
 | 
					  // Determine total steps (6 if pushing, 5 if not)
 | 
				
			||||||
  const commitInfo = new plugins.commitinfo.CommitInfo(paths.cwd, commitVersionType);
 | 
					  const totalSteps = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ? 6 : 5;
 | 
				
			||||||
  await commitInfo.writeIntoPotentialDirs();
 | 
					  let currentStep = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  logger.log('info', `Writing changelog.md ...`);
 | 
					  // Step 1: Baking commitinfo
 | 
				
			||||||
 | 
					  currentStep++;
 | 
				
			||||||
 | 
					  ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'in-progress');
 | 
				
			||||||
 | 
					  const commitInfo = new plugins.commitinfo.CommitInfo(
 | 
				
			||||||
 | 
					    paths.cwd,
 | 
				
			||||||
 | 
					    commitVersionType,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  await commitInfo.writeIntoPotentialDirs();
 | 
				
			||||||
 | 
					  ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'done');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Step 2: Writing changelog
 | 
				
			||||||
 | 
					  currentStep++;
 | 
				
			||||||
 | 
					  ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'in-progress');
 | 
				
			||||||
  let changelog = nextCommitObject.changelog;
 | 
					  let changelog = nextCommitObject.changelog;
 | 
				
			||||||
  changelog = changelog.replaceAll('{{nextVersion}}', (await commitInfo.getNextPlannedVersion()).versionString);
 | 
					  changelog = changelog.replaceAll(
 | 
				
			||||||
  changelog = changelog.replaceAll('{{nextVersionScope}}', `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`);
 | 
					    '{{nextVersion}}',
 | 
				
			||||||
  changelog = changelog.replaceAll('{{nextVersionMessage}}', nextCommitObject.recommendedNextVersionMessage);
 | 
					    (await commitInfo.getNextPlannedVersion()).versionString,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  changelog = changelog.replaceAll(
 | 
				
			||||||
 | 
					    '{{nextVersionScope}}',
 | 
				
			||||||
 | 
					    `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  changelog = changelog.replaceAll(
 | 
				
			||||||
 | 
					    '{{nextVersionMessage}}',
 | 
				
			||||||
 | 
					    nextCommitObject.recommendedNextVersionMessage,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  if (nextCommitObject.recommendedNextVersionDetails?.length > 0) {
 | 
					  if (nextCommitObject.recommendedNextVersionDetails?.length > 0) {
 | 
				
			||||||
    changelog = changelog.replaceAll('{{nextVersionDetails}}', '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '));
 | 
					    changelog = changelog.replaceAll(
 | 
				
			||||||
 | 
					      '{{nextVersionDetails}}',
 | 
				
			||||||
 | 
					      '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    changelog = changelog.replaceAll('\n{{nextVersionDetails}}', '');
 | 
					    changelog = changelog.replaceAll('\n{{nextVersionDetails}}', '');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await plugins.smartfile.memory.toFs(changelog, plugins.path.join(paths.cwd, `changelog.md`));
 | 
					  await plugins.smartfile.memory.toFs(
 | 
				
			||||||
 | 
					    changelog,
 | 
				
			||||||
 | 
					    plugins.path.join(paths.cwd, `changelog.md`),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'done');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  logger.log('info', `Staging files for commit:`);
 | 
					  // Step 3: Staging files
 | 
				
			||||||
 | 
					  currentStep++;
 | 
				
			||||||
 | 
					  ui.printStep(currentStep, totalSteps, '📦 Staging files', 'in-progress');
 | 
				
			||||||
  await smartshellInstance.exec(`git add -A`);
 | 
					  await smartshellInstance.exec(`git add -A`);
 | 
				
			||||||
 | 
					  ui.printStep(currentStep, totalSteps, '📦 Staging files', 'done');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Step 4: Creating commit
 | 
				
			||||||
 | 
					  currentStep++;
 | 
				
			||||||
 | 
					  ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'in-progress');
 | 
				
			||||||
  await smartshellInstance.exec(`git commit -m "${commitString}"`);
 | 
					  await smartshellInstance.exec(`git commit -m "${commitString}"`);
 | 
				
			||||||
  await smartshellInstance.exec(`npm version ${commitVersionType}`);
 | 
					  ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'done');
 | 
				
			||||||
  if (answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true')) {
 | 
					
 | 
				
			||||||
    await smartshellInstance.exec(`git push origin master --follow-tags`);
 | 
					  // Step 5: Bumping version
 | 
				
			||||||
 | 
					  currentStep++;
 | 
				
			||||||
 | 
					  const projectType = await helpers.detectProjectType();
 | 
				
			||||||
 | 
					  const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Step 6: Push to remote (optional)
 | 
				
			||||||
 | 
					  const currentBranch = await helpers.detectCurrentBranch();
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    answerBucket.getAnswerFor('pushToOrigin') &&
 | 
				
			||||||
 | 
					    !(process.env.CI === 'true')
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    currentStep++;
 | 
				
			||||||
 | 
					    ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress');
 | 
				
			||||||
 | 
					    await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`);
 | 
				
			||||||
 | 
					    ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'done');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log(''); // Add spacing before summary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Get commit SHA for summary
 | 
				
			||||||
 | 
					  const commitShaResult = await smartshellInstance.exec('git rev-parse --short HEAD');
 | 
				
			||||||
 | 
					  const commitSha = commitShaResult.stdout.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Print final summary
 | 
				
			||||||
 | 
					  ui.printSummary({
 | 
				
			||||||
 | 
					    projectType,
 | 
				
			||||||
 | 
					    branch: currentBranch,
 | 
				
			||||||
 | 
					    commitType: answerBucket.getAnswerFor('commitType'),
 | 
				
			||||||
 | 
					    commitScope: answerBucket.getAnswerFor('commitScope'),
 | 
				
			||||||
 | 
					    commitMessage: answerBucket.getAnswerFor('commitDescription'),
 | 
				
			||||||
 | 
					    newVersion: newVersion,
 | 
				
			||||||
 | 
					    commitSha: commitSha,
 | 
				
			||||||
 | 
					    pushed: answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true'),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createCommitStringFromAnswerBucket = (answerBucket: plugins.smartinteract.AnswerBucket) => {
 | 
					const createCommitStringFromAnswerBucket = (
 | 
				
			||||||
 | 
					  answerBucket: plugins.smartinteract.AnswerBucket,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
  const commitType = answerBucket.getAnswerFor('commitType');
 | 
					  const commitType = answerBucket.getAnswerFor('commitType');
 | 
				
			||||||
  const commitScope = answerBucket.getAnswerFor('commitScope');
 | 
					  const commitScope = answerBucket.getAnswerFor('commitScope');
 | 
				
			||||||
  const commitDescription = answerBucket.getAnswerFor('commitDescription');
 | 
					  const commitDescription = answerBucket.getAnswerFor('commitDescription');
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										256
									
								
								ts/mod_commit/mod.helpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								ts/mod_commit/mod.helpers.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,256 @@
 | 
				
			|||||||
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
 | 
					import * as paths from '../paths.js';
 | 
				
			||||||
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					import * as ui from './mod.ui.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ProjectType = 'npm' | 'deno' | 'both' | 'none';
 | 
				
			||||||
 | 
					export type VersionType = 'patch' | 'minor' | 'major';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Detects the current git branch
 | 
				
			||||||
 | 
					 * @returns The current branch name, defaults to 'master' if detection fails
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function detectCurrentBranch(): Promise<string> {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
				
			||||||
 | 
					      executor: 'bash',
 | 
				
			||||||
 | 
					      sourceFilePaths: [],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const result = await smartshellInstance.exec('git branch --show-current');
 | 
				
			||||||
 | 
					    const branchName = result.stdout.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!branchName) {
 | 
				
			||||||
 | 
					      logger.log('warn', 'Could not detect current branch, falling back to "master"');
 | 
				
			||||||
 | 
					      return 'master';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.log('info', `Detected current branch: ${branchName}`);
 | 
				
			||||||
 | 
					    return branchName;
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    logger.log('warn', `Failed to detect branch: ${error.message}, falling back to "master"`);
 | 
				
			||||||
 | 
					    return 'master';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Detects the project type based on presence of package.json and/or deno.json
 | 
				
			||||||
 | 
					 * @returns The project type
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function detectProjectType(): Promise<ProjectType> {
 | 
				
			||||||
 | 
					  const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
 | 
				
			||||||
 | 
					  const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const hasPackageJson = await plugins.smartfile.fs.fileExists(packageJsonPath);
 | 
				
			||||||
 | 
					  const hasDenoJson = await plugins.smartfile.fs.fileExists(denoJsonPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (hasPackageJson && hasDenoJson) {
 | 
				
			||||||
 | 
					    logger.log('info', 'Detected dual project (npm + deno)');
 | 
				
			||||||
 | 
					    return 'both';
 | 
				
			||||||
 | 
					  } else if (hasPackageJson) {
 | 
				
			||||||
 | 
					    logger.log('info', 'Detected npm project');
 | 
				
			||||||
 | 
					    return 'npm';
 | 
				
			||||||
 | 
					  } else if (hasDenoJson) {
 | 
				
			||||||
 | 
					    logger.log('info', 'Detected deno project');
 | 
				
			||||||
 | 
					    return 'deno';
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    throw new Error('No package.json or deno.json found in current directory');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Parses a semantic version string and bumps it according to the version type
 | 
				
			||||||
 | 
					 * @param currentVersion Current version string (e.g., "1.2.3")
 | 
				
			||||||
 | 
					 * @param versionType Type of version bump
 | 
				
			||||||
 | 
					 * @returns New version string
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function calculateNewVersion(currentVersion: string, versionType: VersionType): string {
 | 
				
			||||||
 | 
					  const versionMatch = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!versionMatch) {
 | 
				
			||||||
 | 
					    throw new Error(`Invalid version format: ${currentVersion}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let [, major, minor, patch] = versionMatch.map(Number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch (versionType) {
 | 
				
			||||||
 | 
					    case 'major':
 | 
				
			||||||
 | 
					      major += 1;
 | 
				
			||||||
 | 
					      minor = 0;
 | 
				
			||||||
 | 
					      patch = 0;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 'minor':
 | 
				
			||||||
 | 
					      minor += 1;
 | 
				
			||||||
 | 
					      patch = 0;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 'patch':
 | 
				
			||||||
 | 
					      patch += 1;
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return `${major}.${minor}.${patch}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Bumps the version in deno.json, commits the change, and creates a tag
 | 
				
			||||||
 | 
					 * @param versionType Type of version bump
 | 
				
			||||||
 | 
					 * @returns The new version string
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function bumpDenoVersion(versionType: VersionType): Promise<string> {
 | 
				
			||||||
 | 
					  const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
 | 
				
			||||||
 | 
					  const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
				
			||||||
 | 
					    executor: 'bash',
 | 
				
			||||||
 | 
					    sourceFilePaths: [],
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    // Read deno.json
 | 
				
			||||||
 | 
					    const denoConfig = plugins.smartfile.fs.toObjectSync(
 | 
				
			||||||
 | 
					      denoJsonPath
 | 
				
			||||||
 | 
					    ) as { version?: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!denoConfig.version) {
 | 
				
			||||||
 | 
					      throw new Error('deno.json does not contain a version field');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const currentVersion = denoConfig.version;
 | 
				
			||||||
 | 
					    const newVersion = calculateNewVersion(currentVersion, versionType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.log('info', `Bumping deno.json version: ${currentVersion} → ${newVersion}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Update version
 | 
				
			||||||
 | 
					    denoConfig.version = newVersion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Write back to disk
 | 
				
			||||||
 | 
					    await plugins.smartfile.memory.toFs(
 | 
				
			||||||
 | 
					      JSON.stringify(denoConfig, null, 2) + '\n',
 | 
				
			||||||
 | 
					      denoJsonPath
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Stage the deno.json file
 | 
				
			||||||
 | 
					    await smartshellInstance.exec('git add deno.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Commit the version bump
 | 
				
			||||||
 | 
					    await smartshellInstance.exec(`git commit -m "v${newVersion}"`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create the version tag
 | 
				
			||||||
 | 
					    await smartshellInstance.exec(`git tag v${newVersion} -m "v${newVersion}"`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.log('info', `Created commit and tag v${newVersion}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return newVersion;
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    throw new Error(`Failed to bump deno.json version: ${error.message}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Bumps the version in package.json using npm version command
 | 
				
			||||||
 | 
					 * @param versionType Type of version bump
 | 
				
			||||||
 | 
					 * @returns The new version string
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function bumpNpmVersion(versionType: VersionType): Promise<string> {
 | 
				
			||||||
 | 
					  const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
				
			||||||
 | 
					    executor: 'bash',
 | 
				
			||||||
 | 
					    sourceFilePaths: [],
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  logger.log('info', `Bumping package.json version using npm version ${versionType}`);
 | 
				
			||||||
 | 
					  const result = await smartshellInstance.exec(`npm version ${versionType}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // npm version returns the new version with a 'v' prefix, e.g., "v1.2.3"
 | 
				
			||||||
 | 
					  const newVersion = result.stdout.trim().replace(/^v/, '');
 | 
				
			||||||
 | 
					  return newVersion;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Syncs the version from package.json to deno.json and amends the npm commit
 | 
				
			||||||
 | 
					 * @param version The version to sync
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function syncVersionToDenoJson(version: string): Promise<void> {
 | 
				
			||||||
 | 
					  const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
 | 
				
			||||||
 | 
					  const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
				
			||||||
 | 
					    executor: 'bash',
 | 
				
			||||||
 | 
					    sourceFilePaths: [],
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const denoConfig = plugins.smartfile.fs.toObjectSync(
 | 
				
			||||||
 | 
					      denoJsonPath
 | 
				
			||||||
 | 
					    ) as { version?: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.log('info', `Syncing version to deno.json: ${version}`);
 | 
				
			||||||
 | 
					    denoConfig.version = version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await plugins.smartfile.memory.toFs(
 | 
				
			||||||
 | 
					      JSON.stringify(denoConfig, null, 2) + '\n',
 | 
				
			||||||
 | 
					      denoJsonPath
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Stage the deno.json file
 | 
				
			||||||
 | 
					    await smartshellInstance.exec('git add deno.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Amend the npm version commit to include deno.json
 | 
				
			||||||
 | 
					    await smartshellInstance.exec('git commit --amend --no-edit');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Re-create the tag with force to update it
 | 
				
			||||||
 | 
					    await smartshellInstance.exec(`git tag -fa v${version} -m "v${version}"`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.log('info', `Amended commit to include deno.json and updated tag v${version}`);
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    throw new Error(`Failed to sync version to deno.json: ${error.message}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Bumps the project version based on project type
 | 
				
			||||||
 | 
					 * @param projectType The detected project type
 | 
				
			||||||
 | 
					 * @param versionType The type of version bump
 | 
				
			||||||
 | 
					 * @param currentStep The current step number for progress display
 | 
				
			||||||
 | 
					 * @param totalSteps The total number of steps for progress display
 | 
				
			||||||
 | 
					 * @returns The new version string
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function bumpProjectVersion(
 | 
				
			||||||
 | 
					  projectType: ProjectType,
 | 
				
			||||||
 | 
					  versionType: VersionType,
 | 
				
			||||||
 | 
					  currentStep?: number,
 | 
				
			||||||
 | 
					  totalSteps?: number
 | 
				
			||||||
 | 
					): Promise<string> {
 | 
				
			||||||
 | 
					  const projectEmoji = projectType === 'npm' ? '📦' : projectType === 'deno' ? '🦕' : '🔀';
 | 
				
			||||||
 | 
					  const description = `🏷️  Bumping version (${projectEmoji} ${projectType})`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (currentStep && totalSteps) {
 | 
				
			||||||
 | 
					    ui.printStep(currentStep, totalSteps, description, 'in-progress');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let newVersion: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch (projectType) {
 | 
				
			||||||
 | 
					    case 'npm':
 | 
				
			||||||
 | 
					      newVersion = await bumpNpmVersion(versionType);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'deno':
 | 
				
			||||||
 | 
					      newVersion = await bumpDenoVersion(versionType);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'both': {
 | 
				
			||||||
 | 
					      // Bump npm version first (it handles git tags)
 | 
				
			||||||
 | 
					      newVersion = await bumpNpmVersion(versionType);
 | 
				
			||||||
 | 
					      // Then sync to deno.json
 | 
				
			||||||
 | 
					      await syncVersionToDenoJson(newVersion);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'none':
 | 
				
			||||||
 | 
					      throw new Error('Cannot bump version: no package.json or deno.json found');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      throw new Error(`Unknown project type: ${projectType}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (currentStep && totalSteps) {
 | 
				
			||||||
 | 
					    ui.printStep(currentStep, totalSteps, description, 'done');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return newVersion;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										196
									
								
								ts/mod_commit/mod.ui.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								ts/mod_commit/mod.ui.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,196 @@
 | 
				
			|||||||
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * UI helper module for beautiful CLI output
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ICommitSummary {
 | 
				
			||||||
 | 
					  projectType: string;
 | 
				
			||||||
 | 
					  branch: string;
 | 
				
			||||||
 | 
					  commitType: string;
 | 
				
			||||||
 | 
					  commitScope: string;
 | 
				
			||||||
 | 
					  commitMessage: string;
 | 
				
			||||||
 | 
					  newVersion: string;
 | 
				
			||||||
 | 
					  commitSha?: string;
 | 
				
			||||||
 | 
					  pushed: boolean;
 | 
				
			||||||
 | 
					  repoUrl?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IRecommendation {
 | 
				
			||||||
 | 
					  recommendedNextVersion: string;
 | 
				
			||||||
 | 
					  recommendedNextVersionLevel: string;
 | 
				
			||||||
 | 
					  recommendedNextVersionScope: string;
 | 
				
			||||||
 | 
					  recommendedNextVersionMessage: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print a header with a box around it
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printHeader(title: string): void {
 | 
				
			||||||
 | 
					  const width = 57;
 | 
				
			||||||
 | 
					  const padding = Math.max(0, width - title.length - 2);
 | 
				
			||||||
 | 
					  const leftPad = Math.floor(padding / 2);
 | 
				
			||||||
 | 
					  const rightPad = padding - leftPad;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log('');
 | 
				
			||||||
 | 
					  console.log('╭─' + '─'.repeat(width) + '─╮');
 | 
				
			||||||
 | 
					  console.log('│  ' + title + ' '.repeat(rightPad + leftPad) + '  │');
 | 
				
			||||||
 | 
					  console.log('╰─' + '─'.repeat(width) + '─╯');
 | 
				
			||||||
 | 
					  console.log('');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print a section with a border
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printSection(title: string, lines: string[]): void {
 | 
				
			||||||
 | 
					  const width = 59;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log('┌─ ' + title + ' ' + '─'.repeat(Math.max(0, width - title.length - 3)) + '┐');
 | 
				
			||||||
 | 
					  console.log('│' + ' '.repeat(width) + '│');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const line of lines) {
 | 
				
			||||||
 | 
					    const padding = width - line.length;
 | 
				
			||||||
 | 
					    console.log('│  ' + line + ' '.repeat(Math.max(0, padding - 2)) + '│');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log('│' + ' '.repeat(width) + '│');
 | 
				
			||||||
 | 
					  console.log('└─' + '─'.repeat(width) + '─┘');
 | 
				
			||||||
 | 
					  console.log('');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print AI recommendations in a nice box
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printRecommendation(recommendation: IRecommendation): void {
 | 
				
			||||||
 | 
					  const lines = [
 | 
				
			||||||
 | 
					    `Suggested Version:  v${recommendation.recommendedNextVersion}`,
 | 
				
			||||||
 | 
					    `Suggested Type:     ${recommendation.recommendedNextVersionLevel}`,
 | 
				
			||||||
 | 
					    `Suggested Scope:    ${recommendation.recommendedNextVersionScope}`,
 | 
				
			||||||
 | 
					    `Suggested Message:  ${recommendation.recommendedNextVersionMessage}`,
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  printSection('📊 AI Recommendations', lines);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print a progress step
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printStep(
 | 
				
			||||||
 | 
					  current: number,
 | 
				
			||||||
 | 
					  total: number,
 | 
				
			||||||
 | 
					  description: string,
 | 
				
			||||||
 | 
					  status: 'in-progress' | 'done' | 'error'
 | 
				
			||||||
 | 
					): void {
 | 
				
			||||||
 | 
					  const statusIcon = status === 'done' ? '✓' : status === 'error' ? '✗' : '⏳';
 | 
				
			||||||
 | 
					  const dots = '.'.repeat(Math.max(0, 40 - description.length));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log(`  [${current}/${total}] ${description}${dots} ${statusIcon}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Clear the line on next update if in progress
 | 
				
			||||||
 | 
					  if (status === 'in-progress') {
 | 
				
			||||||
 | 
					    process.stdout.write('\x1b[1A'); // Move cursor up one line
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get emoji for project type
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function getProjectTypeEmoji(projectType: string): string {
 | 
				
			||||||
 | 
					  switch (projectType) {
 | 
				
			||||||
 | 
					    case 'npm':
 | 
				
			||||||
 | 
					      return '📦 npm';
 | 
				
			||||||
 | 
					    case 'deno':
 | 
				
			||||||
 | 
					      return '🦕 Deno';
 | 
				
			||||||
 | 
					    case 'both':
 | 
				
			||||||
 | 
					      return '🔀 npm + Deno';
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return '❓ Unknown';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get emoji for commit type
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function getCommitTypeEmoji(commitType: string): string {
 | 
				
			||||||
 | 
					  switch (commitType) {
 | 
				
			||||||
 | 
					    case 'fix':
 | 
				
			||||||
 | 
					      return '🔧 fix';
 | 
				
			||||||
 | 
					    case 'feat':
 | 
				
			||||||
 | 
					      return '✨ feat';
 | 
				
			||||||
 | 
					    case 'BREAKING CHANGE':
 | 
				
			||||||
 | 
					      return '💥 BREAKING CHANGE';
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return commitType;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print final commit summary
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printSummary(summary: ICommitSummary): void {
 | 
				
			||||||
 | 
					  const lines = [
 | 
				
			||||||
 | 
					    `Project Type:   ${getProjectTypeEmoji(summary.projectType)}`,
 | 
				
			||||||
 | 
					    `Branch:         🌿 ${summary.branch}`,
 | 
				
			||||||
 | 
					    `Commit Type:    ${getCommitTypeEmoji(summary.commitType)}`,
 | 
				
			||||||
 | 
					    `Scope:          📍 ${summary.commitScope}`,
 | 
				
			||||||
 | 
					    `New Version:    🏷️  v${summary.newVersion}`,
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (summary.commitSha) {
 | 
				
			||||||
 | 
					    lines.push(`Commit SHA:     📌 ${summary.commitSha}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (summary.pushed) {
 | 
				
			||||||
 | 
					    lines.push(`Remote:         ✓ Pushed successfully`);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    lines.push(`Remote:         ⊘ Not pushed (local only)`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (summary.repoUrl && summary.commitSha) {
 | 
				
			||||||
 | 
					    lines.push('');
 | 
				
			||||||
 | 
					    lines.push(`View at: ${summary.repoUrl}/commit/${summary.commitSha}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  printSection('✅ Commit Summary', lines);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (summary.pushed) {
 | 
				
			||||||
 | 
					    console.log('🎉 All done! Your changes are committed and pushed.\n');
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    console.log('✓ Commit created successfully.\n');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print an info message with consistent formatting
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printInfo(message: string): void {
 | 
				
			||||||
 | 
					  console.log(`  ℹ️  ${message}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print a success message
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printSuccess(message: string): void {
 | 
				
			||||||
 | 
					  console.log(`  ✓ ${message}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print a warning message
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printWarning(message: string): void {
 | 
				
			||||||
 | 
					  logger.log('warn', `⚠️  ${message}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print an error message
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printError(message: string): void {
 | 
				
			||||||
 | 
					  logger.log('error', `✗ ${message}`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print commit message being created
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function printCommitMessage(commitString: string): void {
 | 
				
			||||||
 | 
					  console.log(`\n  📝 Commit: ${commitString}\n`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -36,7 +36,10 @@ export const run = async () => {
 | 
				
			|||||||
  const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(',');
 | 
					  const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(',');
 | 
				
			||||||
  const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`);
 | 
					  const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`);
 | 
				
			||||||
  const newPackageName = answerBucket.getAnswerFor(`newPackageName`);
 | 
					  const newPackageName = answerBucket.getAnswerFor(`newPackageName`);
 | 
				
			||||||
  logger.log('info', `Deprecating package ${oldPackageName} in favour of ${newPackageName}`);
 | 
					  logger.log(
 | 
				
			||||||
 | 
					    'info',
 | 
				
			||||||
 | 
					    `Deprecating package ${oldPackageName} in favour of ${newPackageName}`,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
					  const smartshellInstance = new plugins.smartshell.Smartshell({
 | 
				
			||||||
    executor: 'bash',
 | 
					    executor: 'bash',
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,18 +2,15 @@ import * as plugins from './mod.plugins.js';
 | 
				
			|||||||
import { FormatContext } from './classes.formatcontext.js';
 | 
					import { FormatContext } from './classes.formatcontext.js';
 | 
				
			||||||
import type { IPlannedChange } from './interfaces.format.js';
 | 
					import type { IPlannedChange } from './interfaces.format.js';
 | 
				
			||||||
import { Project } from '../classes.project.js';
 | 
					import { Project } from '../classes.project.js';
 | 
				
			||||||
import { ChangeCache } from './classes.changecache.js';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export abstract class BaseFormatter {
 | 
					export abstract class BaseFormatter {
 | 
				
			||||||
  protected context: FormatContext;
 | 
					  protected context: FormatContext;
 | 
				
			||||||
  protected project: Project;
 | 
					  protected project: Project;
 | 
				
			||||||
  protected cache: ChangeCache;
 | 
					 | 
				
			||||||
  protected stats: any; // Will be FormatStats from context
 | 
					  protected stats: any; // Will be FormatStats from context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(context: FormatContext, project: Project) {
 | 
					  constructor(context: FormatContext, project: Project) {
 | 
				
			||||||
    this.context = context;
 | 
					    this.context = context;
 | 
				
			||||||
    this.project = project;
 | 
					    this.project = project;
 | 
				
			||||||
    this.cache = context.getChangeCache();
 | 
					 | 
				
			||||||
    this.stats = context.getFormatStats();
 | 
					    this.stats = context.getFormatStats();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,7 +37,7 @@ export abstract class BaseFormatter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      await this.postExecute();
 | 
					      await this.postExecute();
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      await this.context.rollbackOperation();
 | 
					      // Don't rollback here - let the FormatPlanner handle it
 | 
				
			||||||
      throw error;
 | 
					      throw error;
 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
      this.stats.endModule(this.name, startTime);
 | 
					      this.stats.endModule(this.name, startTime);
 | 
				
			||||||
@@ -56,38 +53,30 @@ export abstract class BaseFormatter {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected async modifyFile(filepath: string, content: string): Promise<void> {
 | 
					  protected async modifyFile(filepath: string, content: string): Promise<void> {
 | 
				
			||||||
    await this.context.trackFileChange(filepath);
 | 
					    // Validate filepath before writing
 | 
				
			||||||
    await plugins.smartfile.memory.toFs(content, filepath);
 | 
					    if (!filepath || filepath.trim() === '') {
 | 
				
			||||||
    await this.cache.updateFileCache(filepath);
 | 
					      throw new Error(`Invalid empty filepath in modifyFile`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Ensure we have a proper path with directory component
 | 
				
			||||||
 | 
					    // If the path has no directory component (e.g., "package.json"), prepend "./"
 | 
				
			||||||
 | 
					    let normalizedPath = filepath;
 | 
				
			||||||
 | 
					    if (!plugins.path.parse(filepath).dir) {
 | 
				
			||||||
 | 
					      normalizedPath = './' + filepath;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await plugins.smartfile.memory.toFs(content, normalizedPath);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected async createFile(filepath: string, content: string): Promise<void> {
 | 
					  protected async createFile(filepath: string, content: string): Promise<void> {
 | 
				
			||||||
    await plugins.smartfile.memory.toFs(content, filepath);
 | 
					    await plugins.smartfile.memory.toFs(content, filepath);
 | 
				
			||||||
    await this.cache.updateFileCache(filepath);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected async deleteFile(filepath: string): Promise<void> {
 | 
					  protected async deleteFile(filepath: string): Promise<void> {
 | 
				
			||||||
    await this.context.trackFileChange(filepath);
 | 
					 | 
				
			||||||
    await plugins.smartfile.fs.remove(filepath);
 | 
					    await plugins.smartfile.fs.remove(filepath);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected async shouldProcessFile(filepath: string): Promise<boolean> {
 | 
					  protected async shouldProcessFile(filepath: string): Promise<boolean> {
 | 
				
			||||||
    const config = new plugins.npmextra.Npmextra();
 | 
					    return true;
 | 
				
			||||||
    const useCache = config.dataFor('gitzone.format.cache.enabled', true);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if (!useCache) {
 | 
					 | 
				
			||||||
      return true; // Process all files if cache is disabled
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    const hasChanged = await this.cache.hasFileChanged(filepath);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // Record cache statistics
 | 
					 | 
				
			||||||
    if (hasChanged) {
 | 
					 | 
				
			||||||
      this.stats.recordCacheMiss();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this.stats.recordCacheHit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return hasChanged;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -29,21 +29,54 @@ export class ChangeCache {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getManifest(): Promise<ICacheManifest> {
 | 
					  async getManifest(): Promise<ICacheManifest> {
 | 
				
			||||||
    const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
 | 
					    const defaultManifest: ICacheManifest = {
 | 
				
			||||||
    if (!exists) {
 | 
					 | 
				
			||||||
      return {
 | 
					 | 
				
			||||||
      version: this.cacheVersion,
 | 
					      version: this.cacheVersion,
 | 
				
			||||||
      lastFormat: 0,
 | 
					      lastFormat: 0,
 | 
				
			||||||
        files: []
 | 
					      files: [],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
 | 
				
			||||||
 | 
					    if (!exists) {
 | 
				
			||||||
 | 
					      return defaultManifest;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
 | 
					    try {
 | 
				
			||||||
    return JSON.parse(content);
 | 
					      const content = plugins.smartfile.fs.toStringSync(this.manifestPath);
 | 
				
			||||||
 | 
					      const manifest = JSON.parse(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Validate the manifest structure
 | 
				
			||||||
 | 
					      if (this.isValidManifest(manifest)) {
 | 
				
			||||||
 | 
					        return manifest;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.warn('Invalid manifest structure, returning default manifest');
 | 
				
			||||||
 | 
					        return defaultManifest;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.warn(
 | 
				
			||||||
 | 
					        `Failed to read cache manifest: ${error.message}, returning default manifest`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      // Try to delete the corrupted file
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await plugins.smartfile.fs.remove(this.manifestPath);
 | 
				
			||||||
 | 
					      } catch (removeError) {
 | 
				
			||||||
 | 
					        // Ignore removal errors
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return defaultManifest;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async saveManifest(manifest: ICacheManifest): Promise<void> {
 | 
					  async saveManifest(manifest: ICacheManifest): Promise<void> {
 | 
				
			||||||
    await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
 | 
					    // Validate before saving
 | 
				
			||||||
 | 
					    if (!this.isValidManifest(manifest)) {
 | 
				
			||||||
 | 
					      throw new Error('Invalid manifest structure, cannot save');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Ensure directory exists
 | 
				
			||||||
 | 
					    await plugins.smartfile.fs.ensureDir(this.cacheDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Write directly with proper JSON stringification
 | 
				
			||||||
 | 
					    const jsonContent = JSON.stringify(manifest, null, 2);
 | 
				
			||||||
 | 
					    await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async hasFileChanged(filePath: string): Promise<boolean> {
 | 
					  async hasFileChanged(filePath: string): Promise<boolean> {
 | 
				
			||||||
@@ -59,21 +92,29 @@ export class ChangeCache {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Get current file stats
 | 
					    // Get current file stats
 | 
				
			||||||
    const stats = await plugins.smartfile.fs.stat(absolutePath);
 | 
					    const stats = await plugins.smartfile.fs.stat(absolutePath);
 | 
				
			||||||
    const content = await plugins.smartfile.fs.toStringSync(absolutePath);
 | 
					
 | 
				
			||||||
 | 
					    // Skip directories
 | 
				
			||||||
 | 
					    if (stats.isDirectory()) {
 | 
				
			||||||
 | 
					      return false; // Directories are not processed
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const content = plugins.smartfile.fs.toStringSync(absolutePath);
 | 
				
			||||||
    const currentChecksum = this.calculateChecksum(content);
 | 
					    const currentChecksum = this.calculateChecksum(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Get cached info
 | 
					    // Get cached info
 | 
				
			||||||
    const manifest = await this.getManifest();
 | 
					    const manifest = await this.getManifest();
 | 
				
			||||||
    const cachedFile = manifest.files.find(f => f.path === filePath);
 | 
					    const cachedFile = manifest.files.find((f) => f.path === filePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!cachedFile) {
 | 
					    if (!cachedFile) {
 | 
				
			||||||
      return true; // Not in cache, so it's changed
 | 
					      return true; // Not in cache, so it's changed
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Compare checksums
 | 
					    // Compare checksums
 | 
				
			||||||
    return cachedFile.checksum !== currentChecksum || 
 | 
					    return (
 | 
				
			||||||
 | 
					      cachedFile.checksum !== currentChecksum ||
 | 
				
			||||||
      cachedFile.size !== stats.size ||
 | 
					      cachedFile.size !== stats.size ||
 | 
				
			||||||
           cachedFile.modified !== stats.mtimeMs;
 | 
					      cachedFile.modified !== stats.mtimeMs
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async updateFileCache(filePath: string): Promise<void> {
 | 
					  async updateFileCache(filePath: string): Promise<void> {
 | 
				
			||||||
@@ -83,18 +124,24 @@ export class ChangeCache {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Get current file stats
 | 
					    // Get current file stats
 | 
				
			||||||
    const stats = await plugins.smartfile.fs.stat(absolutePath);
 | 
					    const stats = await plugins.smartfile.fs.stat(absolutePath);
 | 
				
			||||||
    const content = await plugins.smartfile.fs.toStringSync(absolutePath);
 | 
					
 | 
				
			||||||
 | 
					    // Skip directories
 | 
				
			||||||
 | 
					    if (stats.isDirectory()) {
 | 
				
			||||||
 | 
					      return; // Don't cache directories
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const content = plugins.smartfile.fs.toStringSync(absolutePath);
 | 
				
			||||||
    const checksum = this.calculateChecksum(content);
 | 
					    const checksum = this.calculateChecksum(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Update manifest
 | 
					    // Update manifest
 | 
				
			||||||
    const manifest = await this.getManifest();
 | 
					    const manifest = await this.getManifest();
 | 
				
			||||||
    const existingIndex = manifest.files.findIndex(f => f.path === filePath);
 | 
					    const existingIndex = manifest.files.findIndex((f) => f.path === filePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const cacheEntry: IFileCache = {
 | 
					    const cacheEntry: IFileCache = {
 | 
				
			||||||
      path: filePath,
 | 
					      path: filePath,
 | 
				
			||||||
      checksum,
 | 
					      checksum,
 | 
				
			||||||
      modified: stats.mtimeMs,
 | 
					      modified: stats.mtimeMs,
 | 
				
			||||||
      size: stats.size
 | 
					      size: stats.size,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (existingIndex !== -1) {
 | 
					    if (existingIndex !== -1) {
 | 
				
			||||||
@@ -141,4 +188,36 @@ export class ChangeCache {
 | 
				
			|||||||
  private calculateChecksum(content: string | Buffer): string {
 | 
					  private calculateChecksum(content: string | Buffer): string {
 | 
				
			||||||
    return plugins.crypto.createHash('sha256').update(content).digest('hex');
 | 
					    return plugins.crypto.createHash('sha256').update(content).digest('hex');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private isValidManifest(manifest: any): manifest is ICacheManifest {
 | 
				
			||||||
 | 
					    // Check if manifest has the required structure
 | 
				
			||||||
 | 
					    if (!manifest || typeof manifest !== 'object') {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check required fields
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      typeof manifest.version !== 'string' ||
 | 
				
			||||||
 | 
					      typeof manifest.lastFormat !== 'number' ||
 | 
				
			||||||
 | 
					      !Array.isArray(manifest.files)
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check each file entry
 | 
				
			||||||
 | 
					    for (const file of manifest.files) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        !file ||
 | 
				
			||||||
 | 
					        typeof file !== 'object' ||
 | 
				
			||||||
 | 
					        typeof file.path !== 'string' ||
 | 
				
			||||||
 | 
					        typeof file.checksum !== 'string' ||
 | 
				
			||||||
 | 
					        typeof file.modified !== 'number' ||
 | 
				
			||||||
 | 
					        typeof file.size !== 'number'
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -17,16 +17,23 @@ export class DependencyAnalyzer {
 | 
				
			|||||||
  private initializeDependencies(): void {
 | 
					  private initializeDependencies(): void {
 | 
				
			||||||
    // Define dependencies between format modules
 | 
					    // Define dependencies between format modules
 | 
				
			||||||
    const dependencies = {
 | 
					    const dependencies = {
 | 
				
			||||||
      'cleanup': [],  // No dependencies
 | 
					      cleanup: [], // No dependencies
 | 
				
			||||||
      'npmextra': [],  // No dependencies
 | 
					      npmextra: [], // No dependencies
 | 
				
			||||||
      'license': ['npmextra'],  // Depends on npmextra for config
 | 
					      license: ['npmextra'], // Depends on npmextra for config
 | 
				
			||||||
      'packagejson': ['npmextra'],  // Depends on npmextra for config
 | 
					      packagejson: ['npmextra'], // Depends on npmextra for config
 | 
				
			||||||
      'templates': ['npmextra', 'packagejson'],  // Depends on both
 | 
					      templates: ['npmextra', 'packagejson'], // Depends on both
 | 
				
			||||||
      'gitignore': ['templates'],  // Depends on templates
 | 
					      gitignore: ['templates'], // Depends on templates
 | 
				
			||||||
      'tsconfig': ['packagejson'],  // Depends on package.json
 | 
					      tsconfig: ['packagejson'], // Depends on package.json
 | 
				
			||||||
      'prettier': ['cleanup', 'npmextra', 'packagejson', 'templates', 'gitignore', 'tsconfig'],  // Runs after most others
 | 
					      prettier: [
 | 
				
			||||||
      'readme': ['npmextra', 'packagejson'],  // Depends on project metadata
 | 
					        'cleanup',
 | 
				
			||||||
      'copy': ['npmextra'],  // Depends on config
 | 
					        'npmextra',
 | 
				
			||||||
 | 
					        'packagejson',
 | 
				
			||||||
 | 
					        'templates',
 | 
				
			||||||
 | 
					        'gitignore',
 | 
				
			||||||
 | 
					        'tsconfig',
 | 
				
			||||||
 | 
					      ], // Runs after most others
 | 
				
			||||||
 | 
					      readme: ['npmextra', 'packagejson'], // Depends on project metadata
 | 
				
			||||||
 | 
					      copy: ['npmextra'], // Depends on config
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Initialize all modules
 | 
					    // Initialize all modules
 | 
				
			||||||
@@ -34,7 +41,7 @@ export class DependencyAnalyzer {
 | 
				
			|||||||
      this.moduleDependencies.set(module, {
 | 
					      this.moduleDependencies.set(module, {
 | 
				
			||||||
        module,
 | 
					        module,
 | 
				
			||||||
        dependencies: new Set(deps),
 | 
					        dependencies: new Set(deps),
 | 
				
			||||||
        dependents: new Set()
 | 
					        dependents: new Set(),
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,7 +57,7 @@ export class DependencyAnalyzer {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] {
 | 
					  getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] {
 | 
				
			||||||
    const modulesMap = new Map(modules.map(m => [m.name, m]));
 | 
					    const modulesMap = new Map(modules.map((m) => [m.name, m]));
 | 
				
			||||||
    const executed = new Set<string>();
 | 
					    const executed = new Set<string>();
 | 
				
			||||||
    const groups: BaseFormatter[][] = [];
 | 
					    const groups: BaseFormatter[][] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,8 +75,9 @@ export class DependencyAnalyzer {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check if all dependencies have been executed
 | 
					        // Check if all dependencies have been executed
 | 
				
			||||||
        const allDepsExecuted = Array.from(dependency.dependencies)
 | 
					        const allDepsExecuted = Array.from(dependency.dependencies).every(
 | 
				
			||||||
          .every(dep => executed.has(dep) || !modulesMap.has(dep));
 | 
					          (dep) => executed.has(dep) || !modulesMap.has(dep),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (allDepsExecuted) {
 | 
					        if (allDepsExecuted) {
 | 
				
			||||||
          currentGroup.push(module);
 | 
					          currentGroup.push(module);
 | 
				
			||||||
@@ -85,7 +93,7 @@ export class DependencyAnalyzer {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      currentGroup.forEach(m => executed.add(m.name));
 | 
					      currentGroup.forEach((m) => executed.add(m.name));
 | 
				
			||||||
      groups.push(currentGroup);
 | 
					      groups.push(currentGroup);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,9 +107,11 @@ export class DependencyAnalyzer {
 | 
				
			|||||||
    if (!dep1 || !dep2) return false;
 | 
					    if (!dep1 || !dep2) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check if module1 depends on module2 or vice versa
 | 
					    // Check if module1 depends on module2 or vice versa
 | 
				
			||||||
    return !dep1.dependencies.has(module2) && 
 | 
					    return (
 | 
				
			||||||
 | 
					      !dep1.dependencies.has(module2) &&
 | 
				
			||||||
      !dep2.dependencies.has(module1) &&
 | 
					      !dep2.dependencies.has(module1) &&
 | 
				
			||||||
      !dep1.dependents.has(module2) &&
 | 
					      !dep1.dependents.has(module2) &&
 | 
				
			||||||
           !dep2.dependents.has(module1);
 | 
					      !dep2.dependents.has(module1)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -5,7 +5,11 @@ import { logger } from '../gitzone.logging.js';
 | 
				
			|||||||
export class DiffReporter {
 | 
					export class DiffReporter {
 | 
				
			||||||
  private diffs: Map<string, string> = new Map();
 | 
					  private diffs: Map<string, string> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async generateDiff(filePath: string, oldContent: string, newContent: string): Promise<string> {
 | 
					  async generateDiff(
 | 
				
			||||||
 | 
					    filePath: string,
 | 
				
			||||||
 | 
					    oldContent: string,
 | 
				
			||||||
 | 
					    newContent: string,
 | 
				
			||||||
 | 
					  ): Promise<string> {
 | 
				
			||||||
    const diff = plugins.smartdiff.createDiff(oldContent, newContent);
 | 
					    const diff = plugins.smartdiff.createDiff(oldContent, newContent);
 | 
				
			||||||
    this.diffs.set(filePath, diff);
 | 
					    this.diffs.set(filePath, diff);
 | 
				
			||||||
    return diff;
 | 
					    return diff;
 | 
				
			||||||
@@ -22,16 +26,25 @@ export class DiffReporter {
 | 
				
			|||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const currentContent = await plugins.smartfile.fs.toStringSync(change.path);
 | 
					      const currentContent = await plugins.smartfile.fs.toStringSync(
 | 
				
			||||||
 | 
					        change.path,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // For planned changes, we need the new content
 | 
					      // For planned changes, we need the new content
 | 
				
			||||||
      if (!change.content) {
 | 
					      if (!change.content) {
 | 
				
			||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return await this.generateDiff(change.path, currentContent, change.content);
 | 
					      return await this.generateDiff(
 | 
				
			||||||
 | 
					        change.path,
 | 
				
			||||||
 | 
					        currentContent,
 | 
				
			||||||
 | 
					        change.content,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      logger.log('error', `Failed to generate diff for ${change.path}: ${error.message}`);
 | 
					      logger.log(
 | 
				
			||||||
 | 
					        'error',
 | 
				
			||||||
 | 
					        `Failed to generate diff for ${change.path}: ${error.message}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      return null;
 | 
					      return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -69,7 +82,7 @@ export class DiffReporter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private colorDiff(diff: string): string {
 | 
					  private colorDiff(diff: string): string {
 | 
				
			||||||
    const lines = diff.split('\n');
 | 
					    const lines = diff.split('\n');
 | 
				
			||||||
    const coloredLines = lines.map(line => {
 | 
					    const coloredLines = lines.map((line) => {
 | 
				
			||||||
      if (line.startsWith('+') && !line.startsWith('+++')) {
 | 
					      if (line.startsWith('+') && !line.startsWith('+++')) {
 | 
				
			||||||
        return `\x1b[32m${line}\x1b[0m`; // Green for additions
 | 
					        return `\x1b[32m${line}\x1b[0m`; // Green for additions
 | 
				
			||||||
      } else if (line.startsWith('-') && !line.startsWith('---')) {
 | 
					      } else if (line.startsWith('-') && !line.startsWith('---')) {
 | 
				
			||||||
@@ -90,11 +103,14 @@ export class DiffReporter {
 | 
				
			|||||||
      totalFiles: this.diffs.size,
 | 
					      totalFiles: this.diffs.size,
 | 
				
			||||||
      diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({
 | 
					      diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({
 | 
				
			||||||
        path,
 | 
					        path,
 | 
				
			||||||
        diff
 | 
					        diff,
 | 
				
			||||||
      }))
 | 
					      })),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath);
 | 
					    await plugins.smartfile.memory.toFs(
 | 
				
			||||||
 | 
					      JSON.stringify(report, null, 2),
 | 
				
			||||||
 | 
					      outputPath,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    logger.log('info', `Diff report saved to ${outputPath}`);
 | 
					    logger.log('info', `Diff report saved to ${outputPath}`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,64 +1,13 @@
 | 
				
			|||||||
import * as plugins from './mod.plugins.js';
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
import { RollbackManager } from './classes.rollbackmanager.js';
 | 
					 | 
				
			||||||
import { ChangeCache } from './classes.changecache.js';
 | 
					 | 
				
			||||||
import { FormatStats } from './classes.formatstats.js';
 | 
					import { FormatStats } from './classes.formatstats.js';
 | 
				
			||||||
import type { IFormatOperation, IFormatPlan } from './interfaces.format.js';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FormatContext {
 | 
					export class FormatContext {
 | 
				
			||||||
  private rollbackManager: RollbackManager;
 | 
					 | 
				
			||||||
  private currentOperation: IFormatOperation | null = null;
 | 
					 | 
				
			||||||
  private changeCache: ChangeCache;
 | 
					 | 
				
			||||||
  private formatStats: FormatStats;
 | 
					  private formatStats: FormatStats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    this.rollbackManager = new RollbackManager();
 | 
					 | 
				
			||||||
    this.changeCache = new ChangeCache();
 | 
					 | 
				
			||||||
    this.formatStats = new FormatStats();
 | 
					    this.formatStats = new FormatStats();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async beginOperation(): Promise<void> {
 | 
					 | 
				
			||||||
    this.currentOperation = await this.rollbackManager.createOperation();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  async trackFileChange(filepath: string): Promise<void> {
 | 
					 | 
				
			||||||
    if (!this.currentOperation) {
 | 
					 | 
				
			||||||
      throw new Error('No operation in progress. Call beginOperation() first.');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    await this.rollbackManager.backupFile(filepath, this.currentOperation.id);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  async commitOperation(): Promise<void> {
 | 
					 | 
				
			||||||
    if (!this.currentOperation) {
 | 
					 | 
				
			||||||
      throw new Error('No operation in progress. Call beginOperation() first.');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    await this.rollbackManager.markComplete(this.currentOperation.id);
 | 
					 | 
				
			||||||
    this.currentOperation = null;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  async rollbackOperation(): Promise<void> {
 | 
					 | 
				
			||||||
    if (!this.currentOperation) {
 | 
					 | 
				
			||||||
      throw new Error('No operation in progress. Call beginOperation() first.');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    await this.rollbackManager.rollback(this.currentOperation.id);
 | 
					 | 
				
			||||||
    this.currentOperation = null;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  async rollbackTo(operationId: string): Promise<void> {
 | 
					 | 
				
			||||||
    await this.rollbackManager.rollback(operationId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  getRollbackManager(): RollbackManager {
 | 
					 | 
				
			||||||
    return this.rollbackManager;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  getChangeCache(): ChangeCache {
 | 
					 | 
				
			||||||
    return this.changeCache;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  async initializeCache(): Promise<void> {
 | 
					 | 
				
			||||||
    await this.changeCache.initialize();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  getFormatStats(): FormatStats {
 | 
					  getFormatStats(): FormatStats {
 | 
				
			||||||
    return this.formatStats;
 | 
					    return this.formatStats;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,10 +18,10 @@ export class FormatPlanner {
 | 
				
			|||||||
        filesAdded: 0,
 | 
					        filesAdded: 0,
 | 
				
			||||||
        filesModified: 0,
 | 
					        filesModified: 0,
 | 
				
			||||||
        filesRemoved: 0,
 | 
					        filesRemoved: 0,
 | 
				
			||||||
        estimatedTime: 0
 | 
					        estimatedTime: 0,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      changes: [],
 | 
					      changes: [],
 | 
				
			||||||
      warnings: []
 | 
					      warnings: [],
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const module of modules) {
 | 
					    for (const module of modules) {
 | 
				
			||||||
@@ -49,45 +49,30 @@ export class FormatPlanner {
 | 
				
			|||||||
        plan.warnings.push({
 | 
					        plan.warnings.push({
 | 
				
			||||||
          level: 'error',
 | 
					          level: 'error',
 | 
				
			||||||
          message: `Failed to analyze module ${module.name}: ${error.message}`,
 | 
					          message: `Failed to analyze module ${module.name}: ${error.message}`,
 | 
				
			||||||
          module: module.name
 | 
					          module: module.name,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    plan.summary.totalFiles = plan.summary.filesAdded + plan.summary.filesModified + plan.summary.filesRemoved;
 | 
					    plan.summary.totalFiles =
 | 
				
			||||||
 | 
					      plan.summary.filesAdded +
 | 
				
			||||||
 | 
					      plan.summary.filesModified +
 | 
				
			||||||
 | 
					      plan.summary.filesRemoved;
 | 
				
			||||||
    plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
 | 
					    plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return plan;
 | 
					    return plan;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async executePlan(plan: IFormatPlan, modules: BaseFormatter[], context: FormatContext, parallel: boolean = true): Promise<void> {
 | 
					  async executePlan(
 | 
				
			||||||
    await context.beginOperation();
 | 
					    plan: IFormatPlan,
 | 
				
			||||||
 | 
					    modules: BaseFormatter[],
 | 
				
			||||||
 | 
					    context: FormatContext,
 | 
				
			||||||
 | 
					    parallel: boolean = false,
 | 
				
			||||||
 | 
					  ): Promise<void> {
 | 
				
			||||||
    const startTime = Date.now();
 | 
					    const startTime = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (parallel) {
 | 
					      // Always use sequential execution to avoid race conditions
 | 
				
			||||||
        // Get execution groups based on dependencies
 | 
					 | 
				
			||||||
        const executionGroups = this.dependencyAnalyzer.getExecutionGroups(modules);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        logger.log('info', `Executing formatters in ${executionGroups.length} groups...`);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        for (let i = 0; i < executionGroups.length; i++) {
 | 
					 | 
				
			||||||
          const group = executionGroups[i];
 | 
					 | 
				
			||||||
          logger.log('info', `Executing group ${i + 1}: ${group.map(m => m.name).join(', ')}`);
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          // Execute modules in this group in parallel
 | 
					 | 
				
			||||||
          const promises = group.map(async (module) => {
 | 
					 | 
				
			||||||
            const changes = this.plannedChanges.get(module.name) || [];
 | 
					 | 
				
			||||||
            if (changes.length > 0) {
 | 
					 | 
				
			||||||
              logger.log('info', `Executing ${module.name} formatter...`);
 | 
					 | 
				
			||||||
              await module.execute(changes);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
          
 | 
					 | 
				
			||||||
          await Promise.all(promises);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        // Sequential execution (original implementation)
 | 
					 | 
				
			||||||
      for (const module of modules) {
 | 
					      for (const module of modules) {
 | 
				
			||||||
        const changes = this.plannedChanges.get(module.name) || [];
 | 
					        const changes = this.plannedChanges.get(module.name) || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -96,20 +81,19 @@ export class FormatPlanner {
 | 
				
			|||||||
          await module.execute(changes);
 | 
					          await module.execute(changes);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const endTime = Date.now();
 | 
					      const endTime = Date.now();
 | 
				
			||||||
      const duration = endTime - startTime;
 | 
					      const duration = endTime - startTime;
 | 
				
			||||||
      logger.log('info', `Format operations completed in ${duration}ms`);
 | 
					      logger.log('info', `Format operations completed in ${duration}ms`);
 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      await context.commitOperation();
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      await context.rollbackOperation();
 | 
					 | 
				
			||||||
      throw error;
 | 
					      throw error;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async displayPlan(plan: IFormatPlan, detailed: boolean = false): Promise<void> {
 | 
					  async displayPlan(
 | 
				
			||||||
 | 
					    plan: IFormatPlan,
 | 
				
			||||||
 | 
					    detailed: boolean = false,
 | 
				
			||||||
 | 
					  ): Promise<void> {
 | 
				
			||||||
    console.log('\nFormat Plan:');
 | 
					    console.log('\nFormat Plan:');
 | 
				
			||||||
    console.log('━'.repeat(50));
 | 
					    console.log('━'.repeat(50));
 | 
				
			||||||
    console.log(`Summary: ${plan.summary.totalFiles} files will be changed`);
 | 
					    console.log(`Summary: ${plan.summary.totalFiles} files will be changed`);
 | 
				
			||||||
@@ -128,7 +112,9 @@ export class FormatPlanner {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const [module, changes] of changesByModule) {
 | 
					    for (const [module, changes] of changesByModule) {
 | 
				
			||||||
      console.log(`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`);
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (const change of changes) {
 | 
					      for (const change of changes) {
 | 
				
			||||||
        const icon = this.getChangeIcon(change.type);
 | 
					        const icon = this.getChangeIcon(change.type);
 | 
				
			||||||
@@ -157,16 +143,16 @@ export class FormatPlanner {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private getModuleIcon(module: string): string {
 | 
					  private getModuleIcon(module: string): string {
 | 
				
			||||||
    const icons: Record<string, string> = {
 | 
					    const icons: Record<string, string> = {
 | 
				
			||||||
      'packagejson': '📦',
 | 
					      packagejson: '📦',
 | 
				
			||||||
      'license': '📝',
 | 
					      license: '📝',
 | 
				
			||||||
      'tsconfig': '🔧',
 | 
					      tsconfig: '🔧',
 | 
				
			||||||
      'cleanup': '🚮',
 | 
					      cleanup: '🚮',
 | 
				
			||||||
      'gitignore': '🔒',
 | 
					      gitignore: '🔒',
 | 
				
			||||||
      'prettier': '✨',
 | 
					      prettier: '✨',
 | 
				
			||||||
      'readme': '📖',
 | 
					      readme: '📖',
 | 
				
			||||||
      'templates': '📄',
 | 
					      templates: '📄',
 | 
				
			||||||
      'npmextra': '⚙️',
 | 
					      npmextra: '⚙️',
 | 
				
			||||||
      'copy': '📋'
 | 
					      copy: '📋',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    return icons[module] || '📁';
 | 
					    return icons[module] || '📁';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,8 +44,8 @@ export class FormatStats {
 | 
				
			|||||||
        totalDeleted: 0,
 | 
					        totalDeleted: 0,
 | 
				
			||||||
        totalErrors: 0,
 | 
					        totalErrors: 0,
 | 
				
			||||||
        cacheHits: 0,
 | 
					        cacheHits: 0,
 | 
				
			||||||
        cacheMisses: 0
 | 
					        cacheMisses: 0,
 | 
				
			||||||
      }
 | 
					      },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,7 +58,7 @@ export class FormatStats {
 | 
				
			|||||||
      successes: 0,
 | 
					      successes: 0,
 | 
				
			||||||
      filesCreated: 0,
 | 
					      filesCreated: 0,
 | 
				
			||||||
      filesModified: 0,
 | 
					      filesModified: 0,
 | 
				
			||||||
      filesDeleted: 0
 | 
					      filesDeleted: 0,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,7 +73,11 @@ export class FormatStats {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  recordFileOperation(moduleName: string, operation: 'create' | 'modify' | 'delete', success: boolean = true): void {
 | 
					  recordFileOperation(
 | 
				
			||||||
 | 
					    moduleName: string,
 | 
				
			||||||
 | 
					    operation: 'create' | 'modify' | 'delete',
 | 
				
			||||||
 | 
					    success: boolean = true,
 | 
				
			||||||
 | 
					  ): void {
 | 
				
			||||||
    const moduleStats = this.stats.moduleStats.get(moduleName);
 | 
					    const moduleStats = this.stats.moduleStats.get(moduleName);
 | 
				
			||||||
    if (!moduleStats) return;
 | 
					    if (!moduleStats) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,16 +126,24 @@ export class FormatStats {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Overall stats
 | 
					    // Overall stats
 | 
				
			||||||
    console.log('\nOverall Summary:');
 | 
					    console.log('\nOverall Summary:');
 | 
				
			||||||
    console.log(`  Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`);
 | 
					    console.log(
 | 
				
			||||||
 | 
					      `  Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    console.log(`  Files Processed: ${this.stats.overallStats.totalFiles}`);
 | 
					    console.log(`  Files Processed: ${this.stats.overallStats.totalFiles}`);
 | 
				
			||||||
    console.log(`    • Created: ${this.stats.overallStats.totalCreated}`);
 | 
					    console.log(`    • Created: ${this.stats.overallStats.totalCreated}`);
 | 
				
			||||||
    console.log(`    • Modified: ${this.stats.overallStats.totalModified}`);
 | 
					    console.log(`    • Modified: ${this.stats.overallStats.totalModified}`);
 | 
				
			||||||
    console.log(`    • Deleted: ${this.stats.overallStats.totalDeleted}`);
 | 
					    console.log(`    • Deleted: ${this.stats.overallStats.totalDeleted}`);
 | 
				
			||||||
    console.log(`  Errors: ${this.stats.overallStats.totalErrors}`);
 | 
					    console.log(`  Errors: ${this.stats.overallStats.totalErrors}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.stats.overallStats.cacheHits > 0 || this.stats.overallStats.cacheMisses > 0) {
 | 
					    if (
 | 
				
			||||||
      const cacheHitRate = this.stats.overallStats.cacheHits / 
 | 
					      this.stats.overallStats.cacheHits > 0 ||
 | 
				
			||||||
        (this.stats.overallStats.cacheHits + this.stats.overallStats.cacheMisses) * 100;
 | 
					      this.stats.overallStats.cacheMisses > 0
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      const cacheHitRate =
 | 
				
			||||||
 | 
					        (this.stats.overallStats.cacheHits /
 | 
				
			||||||
 | 
					          (this.stats.overallStats.cacheHits +
 | 
				
			||||||
 | 
					            this.stats.overallStats.cacheMisses)) *
 | 
				
			||||||
 | 
					        100;
 | 
				
			||||||
      console.log(`  Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
 | 
					      console.log(`  Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
 | 
				
			||||||
      console.log(`    • Hits: ${this.stats.overallStats.cacheHits}`);
 | 
					      console.log(`    • Hits: ${this.stats.overallStats.cacheHits}`);
 | 
				
			||||||
      console.log(`    • Misses: ${this.stats.overallStats.cacheMisses}`);
 | 
					      console.log(`    • Misses: ${this.stats.overallStats.cacheMisses}`);
 | 
				
			||||||
@@ -141,12 +153,17 @@ export class FormatStats {
 | 
				
			|||||||
    console.log('\nModule Breakdown:');
 | 
					    console.log('\nModule Breakdown:');
 | 
				
			||||||
    console.log('─'.repeat(50));
 | 
					    console.log('─'.repeat(50));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sortedModules = Array.from(this.stats.moduleStats.values())
 | 
					    const sortedModules = Array.from(this.stats.moduleStats.values()).sort(
 | 
				
			||||||
      .sort((a, b) => b.filesProcessed - a.filesProcessed);
 | 
					      (a, b) => b.filesProcessed - a.filesProcessed,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const moduleStats of sortedModules) {
 | 
					    for (const moduleStats of sortedModules) {
 | 
				
			||||||
      console.log(`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`);
 | 
					      console.log(
 | 
				
			||||||
      console.log(`  Execution Time: ${this.formatDuration(moduleStats.executionTime)}`);
 | 
					        `\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `  Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      console.log(`  Files Processed: ${moduleStats.filesProcessed}`);
 | 
					      console.log(`  Files Processed: ${moduleStats.filesProcessed}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (moduleStats.filesCreated > 0) {
 | 
					      if (moduleStats.filesCreated > 0) {
 | 
				
			||||||
@@ -172,10 +189,13 @@ export class FormatStats {
 | 
				
			|||||||
      timestamp: new Date().toISOString(),
 | 
					      timestamp: new Date().toISOString(),
 | 
				
			||||||
      executionTime: this.stats.totalExecutionTime,
 | 
					      executionTime: this.stats.totalExecutionTime,
 | 
				
			||||||
      overallStats: this.stats.overallStats,
 | 
					      overallStats: this.stats.overallStats,
 | 
				
			||||||
      moduleStats: Array.from(this.stats.moduleStats.values())
 | 
					      moduleStats: Array.from(this.stats.moduleStats.values()),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath);
 | 
					    await plugins.smartfile.memory.toFs(
 | 
				
			||||||
 | 
					      JSON.stringify(report, null, 2),
 | 
				
			||||||
 | 
					      outputPath,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    logger.log('info', `Statistics report saved to ${outputPath}`);
 | 
					    logger.log('info', `Statistics report saved to ${outputPath}`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -193,16 +213,16 @@ export class FormatStats {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private getModuleIcon(module: string): string {
 | 
					  private getModuleIcon(module: string): string {
 | 
				
			||||||
    const icons: Record<string, string> = {
 | 
					    const icons: Record<string, string> = {
 | 
				
			||||||
      'packagejson': '📦',
 | 
					      packagejson: '📦',
 | 
				
			||||||
      'license': '📝',
 | 
					      license: '📝',
 | 
				
			||||||
      'tsconfig': '🔧',
 | 
					      tsconfig: '🔧',
 | 
				
			||||||
      'cleanup': '🚮',
 | 
					      cleanup: '🚮',
 | 
				
			||||||
      'gitignore': '🔒',
 | 
					      gitignore: '🔒',
 | 
				
			||||||
      'prettier': '✨',
 | 
					      prettier: '✨',
 | 
				
			||||||
      'readme': '📖',
 | 
					      readme: '📖',
 | 
				
			||||||
      'templates': '📄',
 | 
					      templates: '📄',
 | 
				
			||||||
      'npmextra': '⚙️',
 | 
					      npmextra: '⚙️',
 | 
				
			||||||
      'copy': '📋'
 | 
					      copy: '📋',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    return icons[module] || '📁';
 | 
					    return icons[module] || '📁';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ export class RollbackManager {
 | 
				
			|||||||
      id: this.generateOperationId(),
 | 
					      id: this.generateOperationId(),
 | 
				
			||||||
      timestamp: Date.now(),
 | 
					      timestamp: Date.now(),
 | 
				
			||||||
      files: [],
 | 
					      files: [],
 | 
				
			||||||
      status: 'pending'
 | 
					      status: 'pending',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.updateManifest(operation);
 | 
					    await this.updateManifest(operation);
 | 
				
			||||||
@@ -43,7 +43,7 @@ export class RollbackManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Read file content and metadata
 | 
					    // Read file content and metadata
 | 
				
			||||||
    const content = await plugins.smartfile.fs.toStringSync(absolutePath);
 | 
					    const content = plugins.smartfile.fs.toStringSync(absolutePath);
 | 
				
			||||||
    const stats = await plugins.smartfile.fs.stat(absolutePath);
 | 
					    const stats = await plugins.smartfile.fs.stat(absolutePath);
 | 
				
			||||||
    const checksum = this.calculateChecksum(content);
 | 
					    const checksum = this.calculateChecksum(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -57,7 +57,7 @@ export class RollbackManager {
 | 
				
			|||||||
      path: filepath,
 | 
					      path: filepath,
 | 
				
			||||||
      originalContent: content,
 | 
					      originalContent: content,
 | 
				
			||||||
      checksum,
 | 
					      checksum,
 | 
				
			||||||
      permissions: stats.mode.toString(8)
 | 
					      permissions: stats.mode.toString(8),
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.updateManifest(operation);
 | 
					    await this.updateManifest(operation);
 | 
				
			||||||
@@ -66,7 +66,9 @@ export class RollbackManager {
 | 
				
			|||||||
  async rollback(operationId: string): Promise<void> {
 | 
					  async rollback(operationId: string): Promise<void> {
 | 
				
			||||||
    const operation = await this.getOperation(operationId);
 | 
					    const operation = await this.getOperation(operationId);
 | 
				
			||||||
    if (!operation) {
 | 
					    if (!operation) {
 | 
				
			||||||
      throw new Error(`Operation ${operationId} not found`);
 | 
					      // Operation doesn't exist, might have already been rolled back or never created
 | 
				
			||||||
 | 
					      console.warn(`Operation ${operationId} not found for rollback, skipping`);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (operation.status === 'rolled-back') {
 | 
					    if (operation.status === 'rolled-back') {
 | 
				
			||||||
@@ -82,7 +84,7 @@ export class RollbackManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // Verify backup integrity
 | 
					      // Verify backup integrity
 | 
				
			||||||
      const backupPath = this.getBackupPath(operationId, file.path);
 | 
					      const backupPath = this.getBackupPath(operationId, file.path);
 | 
				
			||||||
      const backupContent = await plugins.smartfile.fs.toStringSync(backupPath);
 | 
					      const backupContent = plugins.smartfile.fs.toStringSync(backupPath);
 | 
				
			||||||
      const backupChecksum = this.calculateChecksum(backupContent);
 | 
					      const backupChecksum = this.calculateChecksum(backupContent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (backupChecksum !== file.checksum) {
 | 
					      if (backupChecksum !== file.checksum) {
 | 
				
			||||||
@@ -114,19 +116,25 @@ export class RollbackManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  async cleanOldBackups(retentionDays: number): Promise<void> {
 | 
					  async cleanOldBackups(retentionDays: number): Promise<void> {
 | 
				
			||||||
    const manifest = await this.getManifest();
 | 
					    const manifest = await this.getManifest();
 | 
				
			||||||
    const cutoffTime = Date.now() - (retentionDays * 24 * 60 * 60 * 1000);
 | 
					    const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const operationsToDelete = manifest.operations.filter(op => 
 | 
					    const operationsToDelete = manifest.operations.filter(
 | 
				
			||||||
      op.timestamp < cutoffTime && op.status === 'completed'
 | 
					      (op) => op.timestamp < cutoffTime && op.status === 'completed',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const operation of operationsToDelete) {
 | 
					    for (const operation of operationsToDelete) {
 | 
				
			||||||
      // Remove backup files
 | 
					      // Remove backup files
 | 
				
			||||||
      const operationDir = plugins.path.join(this.backupDir, 'operations', operation.id);
 | 
					      const operationDir = plugins.path.join(
 | 
				
			||||||
 | 
					        this.backupDir,
 | 
				
			||||||
 | 
					        'operations',
 | 
				
			||||||
 | 
					        operation.id,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      await plugins.smartfile.fs.remove(operationDir);
 | 
					      await plugins.smartfile.fs.remove(operationDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Remove from manifest
 | 
					      // Remove from manifest
 | 
				
			||||||
      manifest.operations = manifest.operations.filter(op => op.id !== operation.id);
 | 
					      manifest.operations = manifest.operations.filter(
 | 
				
			||||||
 | 
					        (op) => op.id !== operation.id,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.saveManifest(manifest);
 | 
					    await this.saveManifest(manifest);
 | 
				
			||||||
@@ -146,7 +154,7 @@ export class RollbackManager {
 | 
				
			|||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const content = await plugins.smartfile.fs.toStringSync(backupPath);
 | 
					      const content = plugins.smartfile.fs.toStringSync(backupPath);
 | 
				
			||||||
      const checksum = this.calculateChecksum(content);
 | 
					      const checksum = this.calculateChecksum(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (checksum !== file.checksum) {
 | 
					      if (checksum !== file.checksum) {
 | 
				
			||||||
@@ -164,7 +172,9 @@ export class RollbackManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private async ensureBackupDir(): Promise<void> {
 | 
					  private async ensureBackupDir(): Promise<void> {
 | 
				
			||||||
    await plugins.smartfile.fs.ensureDir(this.backupDir);
 | 
					    await plugins.smartfile.fs.ensureDir(this.backupDir);
 | 
				
			||||||
    await plugins.smartfile.fs.ensureDir(plugins.path.join(this.backupDir, 'operations'));
 | 
					    await plugins.smartfile.fs.ensureDir(
 | 
				
			||||||
 | 
					      plugins.path.join(this.backupDir, 'operations'),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private generateOperationId(): string {
 | 
					  private generateOperationId(): string {
 | 
				
			||||||
@@ -177,7 +187,14 @@ export class RollbackManager {
 | 
				
			|||||||
    const filename = plugins.path.basename(filepath);
 | 
					    const filename = plugins.path.basename(filepath);
 | 
				
			||||||
    const dir = plugins.path.dirname(filepath);
 | 
					    const dir = plugins.path.dirname(filepath);
 | 
				
			||||||
    const safeDir = dir.replace(/[/\\]/g, '__');
 | 
					    const safeDir = dir.replace(/[/\\]/g, '__');
 | 
				
			||||||
    return plugins.path.join(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`);
 | 
					    return plugins.path.join(
 | 
				
			||||||
 | 
					      this.backupDir,
 | 
				
			||||||
 | 
					      'operations',
 | 
				
			||||||
 | 
					      operationId,
 | 
				
			||||||
 | 
					      'files',
 | 
				
			||||||
 | 
					      safeDir,
 | 
				
			||||||
 | 
					      `${filename}.backup`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private calculateChecksum(content: string | Buffer): string {
 | 
					  private calculateChecksum(content: string | Buffer): string {
 | 
				
			||||||
@@ -185,27 +202,68 @@ export class RollbackManager {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
 | 
					  private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
 | 
				
			||||||
 | 
					    const defaultManifest = { operations: [] };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
 | 
					    const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
 | 
				
			||||||
    if (!exists) {
 | 
					    if (!exists) {
 | 
				
			||||||
      return { operations: [] };
 | 
					      return defaultManifest;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
 | 
					    try {
 | 
				
			||||||
    return JSON.parse(content);
 | 
					      const content = plugins.smartfile.fs.toStringSync(this.manifestPath);
 | 
				
			||||||
 | 
					      const manifest = JSON.parse(content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Validate the manifest structure
 | 
				
			||||||
 | 
					      if (this.isValidManifest(manifest)) {
 | 
				
			||||||
 | 
					        return manifest;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.warn(
 | 
				
			||||||
 | 
					          'Invalid rollback manifest structure, returning default manifest',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return defaultManifest;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.warn(
 | 
				
			||||||
 | 
					        `Failed to read rollback manifest: ${error.message}, returning default manifest`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      // Try to delete the corrupted file
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await plugins.smartfile.fs.remove(this.manifestPath);
 | 
				
			||||||
 | 
					      } catch (removeError) {
 | 
				
			||||||
 | 
					        // Ignore removal errors
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return defaultManifest;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async saveManifest(manifest: { operations: IFormatOperation[] }): Promise<void> {
 | 
					  private async saveManifest(manifest: {
 | 
				
			||||||
    await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
 | 
					    operations: IFormatOperation[];
 | 
				
			||||||
 | 
					  }): Promise<void> {
 | 
				
			||||||
 | 
					    // Validate before saving
 | 
				
			||||||
 | 
					    if (!this.isValidManifest(manifest)) {
 | 
				
			||||||
 | 
					      throw new Error('Invalid rollback manifest structure, cannot save');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async getOperation(operationId: string): Promise<IFormatOperation | null> {
 | 
					    // Ensure directory exists
 | 
				
			||||||
 | 
					    await this.ensureBackupDir();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Write directly with proper JSON stringification
 | 
				
			||||||
 | 
					    const jsonContent = JSON.stringify(manifest, null, 2);
 | 
				
			||||||
 | 
					    await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async getOperation(
 | 
				
			||||||
 | 
					    operationId: string,
 | 
				
			||||||
 | 
					  ): Promise<IFormatOperation | null> {
 | 
				
			||||||
    const manifest = await this.getManifest();
 | 
					    const manifest = await this.getManifest();
 | 
				
			||||||
    return manifest.operations.find(op => op.id === operationId) || null;
 | 
					    return manifest.operations.find((op) => op.id === operationId) || null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async updateManifest(operation: IFormatOperation): Promise<void> {
 | 
					  private async updateManifest(operation: IFormatOperation): Promise<void> {
 | 
				
			||||||
    const manifest = await this.getManifest();
 | 
					    const manifest = await this.getManifest();
 | 
				
			||||||
    const existingIndex = manifest.operations.findIndex(op => op.id === operation.id);
 | 
					    const existingIndex = manifest.operations.findIndex(
 | 
				
			||||||
 | 
					      (op) => op.id === operation.id,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (existingIndex !== -1) {
 | 
					    if (existingIndex !== -1) {
 | 
				
			||||||
      manifest.operations[existingIndex] = operation;
 | 
					      manifest.operations[existingIndex] = operation;
 | 
				
			||||||
@@ -215,4 +273,46 @@ export class RollbackManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await this.saveManifest(manifest);
 | 
					    await this.saveManifest(manifest);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private isValidManifest(
 | 
				
			||||||
 | 
					    manifest: any,
 | 
				
			||||||
 | 
					  ): manifest is { operations: IFormatOperation[] } {
 | 
				
			||||||
 | 
					    // Check if manifest has the required structure
 | 
				
			||||||
 | 
					    if (!manifest || typeof manifest !== 'object') {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check required fields
 | 
				
			||||||
 | 
					    if (!Array.isArray(manifest.operations)) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check each operation entry
 | 
				
			||||||
 | 
					    for (const operation of manifest.operations) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        !operation ||
 | 
				
			||||||
 | 
					        typeof operation !== 'object' ||
 | 
				
			||||||
 | 
					        typeof operation.id !== 'string' ||
 | 
				
			||||||
 | 
					        typeof operation.timestamp !== 'number' ||
 | 
				
			||||||
 | 
					        typeof operation.status !== 'string' ||
 | 
				
			||||||
 | 
					        !Array.isArray(operation.files)
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check each file in the operation
 | 
				
			||||||
 | 
					      for (const file of operation.files) {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          !file ||
 | 
				
			||||||
 | 
					          typeof file !== 'object' ||
 | 
				
			||||||
 | 
					          typeof file.path !== 'string' ||
 | 
				
			||||||
 | 
					          typeof file.checksum !== 'string'
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -4,14 +4,21 @@ import * as paths from '../paths.js';
 | 
				
			|||||||
import { logger } from '../gitzone.logging.js';
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
import { Project } from '../classes.project.js';
 | 
					import { Project } from '../classes.project.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filesToDelete = ['defaults.yml', 'yarn.lock', 'package-lock.json', 'tslint.json'];
 | 
					const filesToDelete = [
 | 
				
			||||||
 | 
					  'defaults.yml',
 | 
				
			||||||
 | 
					  'yarn.lock',
 | 
				
			||||||
 | 
					  'package-lock.json',
 | 
				
			||||||
 | 
					  'tslint.json',
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const run = async (projectArg: Project) => {
 | 
					export const run = async (projectArg: Project) => {
 | 
				
			||||||
  for (const relativeFilePath of filesToDelete) {
 | 
					  for (const relativeFilePath of filesToDelete) {
 | 
				
			||||||
    const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
 | 
					    const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
 | 
				
			||||||
    if (fileExists) {
 | 
					    if (fileExists) {
 | 
				
			||||||
      logger.log('info', `Found ${relativeFilePath}! Removing it!`);
 | 
					      logger.log('info', `Found ${relativeFilePath}! Removing it!`);
 | 
				
			||||||
      plugins.smartfile.fs.removeSync(plugins.path.join(paths.cwd, relativeFilePath));
 | 
					      plugins.smartfile.fs.removeSync(
 | 
				
			||||||
 | 
					        plugins.path.join(paths.cwd, relativeFilePath),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      logger.log('info', `Project is free of ${relativeFilePath}`);
 | 
					      logger.log('info', `Project is free of ${relativeFilePath}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
  // Get copy configuration from npmextra.json
 | 
					  // Get copy configuration from npmextra.json
 | 
				
			||||||
  const npmextraConfig = new plugins.npmextra.Npmextra();
 | 
					  const npmextraConfig = new plugins.npmextra.Npmextra();
 | 
				
			||||||
  const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', {
 | 
					  const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', {
 | 
				
			||||||
    patterns: []
 | 
					    patterns: [],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
 | 
					  if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
 | 
				
			||||||
@@ -40,7 +40,7 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
        if (pattern.preservePath) {
 | 
					        if (pattern.preservePath) {
 | 
				
			||||||
          const relativePath = plugins.path.relative(
 | 
					          const relativePath = plugins.path.relative(
 | 
				
			||||||
            plugins.path.dirname(pattern.from.replace(/\*/g, '')),
 | 
					            plugins.path.dirname(pattern.from.replace(/\*/g, '')),
 | 
				
			||||||
            file
 | 
					            file,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          destPath = plugins.path.join(pattern.to, relativePath);
 | 
					          destPath = plugins.path.join(pattern.to, relativePath);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -53,7 +53,10 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
        logger.log('info', `Copied ${sourcePath} to ${destPath}`);
 | 
					        logger.log('info', `Copied ${sourcePath} to ${destPath}`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      logger.log('error', `Failed to copy pattern ${pattern.from}: ${error.message}`);
 | 
					      logger.log(
 | 
				
			||||||
 | 
					        'error',
 | 
				
			||||||
 | 
					        `Failed to copy pattern ${pattern.from}: ${error.message}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,14 +8,40 @@ const gitignorePath = plugins.path.join(paths.cwd, './.gitignore');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const run = async (projectArg: Project) => {
 | 
					export const run = async (projectArg: Project) => {
 | 
				
			||||||
  const gitignoreExists = await plugins.smartfile.fs.fileExists(gitignorePath);
 | 
					  const gitignoreExists = await plugins.smartfile.fs.fileExists(gitignorePath);
 | 
				
			||||||
  const templateModule = await import('../mod_template/index.js');
 | 
					  let customContent = '';
 | 
				
			||||||
  const ciTemplate = await templateModule.getTemplate('gitignore');
 | 
					
 | 
				
			||||||
  if (gitignoreExists) {
 | 
					  if (gitignoreExists) {
 | 
				
			||||||
    // lets get the existing gitignore file
 | 
					    // lets get the existing gitignore file
 | 
				
			||||||
    const existingGitIgnoreString = plugins.smartfile.fs.toStringSync(gitignorePath);
 | 
					    const existingGitIgnoreString =
 | 
				
			||||||
    let customPart = existingGitIgnoreString.split('# custom\n')[1];
 | 
					      plugins.smartfile.fs.toStringSync(gitignorePath);
 | 
				
			||||||
    customPart ? null : (customPart = '');
 | 
					
 | 
				
			||||||
 | 
					    // Check for different custom section markers
 | 
				
			||||||
 | 
					    const customMarkers = ['#------# custom', '# custom'];
 | 
				
			||||||
 | 
					    for (const marker of customMarkers) {
 | 
				
			||||||
 | 
					      const splitResult = existingGitIgnoreString.split(marker);
 | 
				
			||||||
 | 
					      if (splitResult.length > 1) {
 | 
				
			||||||
 | 
					        // Get everything after the marker (excluding the marker itself)
 | 
				
			||||||
 | 
					        customContent = splitResult[1].trim();
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
  ciTemplate.writeToDisk(paths.cwd);
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Write the template
 | 
				
			||||||
 | 
					  const templateModule = await import('../mod_template/index.js');
 | 
				
			||||||
 | 
					  const ciTemplate = await templateModule.getTemplate('gitignore');
 | 
				
			||||||
 | 
					  await ciTemplate.writeToDisk(paths.cwd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Append the custom content if it exists
 | 
				
			||||||
 | 
					  if (customContent) {
 | 
				
			||||||
 | 
					    const newGitignoreContent =
 | 
				
			||||||
 | 
					      plugins.smartfile.fs.toStringSync(gitignorePath);
 | 
				
			||||||
 | 
					    // The template already ends with "#------# custom", so just append the content
 | 
				
			||||||
 | 
					    const finalContent =
 | 
				
			||||||
 | 
					      newGitignoreContent.trimEnd() + '\n' + customContent + '\n';
 | 
				
			||||||
 | 
					    await plugins.smartfile.fs.toFs(finalContent, gitignorePath);
 | 
				
			||||||
 | 
					    logger.log('info', 'Updated .gitignore while preserving custom section!');
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
    logger.log('info', 'Added a .gitignore!');
 | 
					    logger.log('info', 'Added a .gitignore!');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,9 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    logger.log('error', 'Error -> licenses failed. Here is why:');
 | 
					    logger.log('error', 'Error -> licenses failed. Here is why:');
 | 
				
			||||||
    for (const failedModule of licenseCheckResult.failingModules) {
 | 
					    for (const failedModule of licenseCheckResult.failingModules) {
 | 
				
			||||||
      console.log(`${failedModule.name} fails with license ${failedModule.license}`);
 | 
					      console.log(
 | 
				
			||||||
 | 
					        `${failedModule.name} fails with license ${failedModule.license}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,12 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      const interactInstance = new plugins.smartinteract.SmartInteract();
 | 
					      const interactInstance = new plugins.smartinteract.SmartInteract();
 | 
				
			||||||
      for (const expectedRepoInformationItem of expectedRepoInformation) {
 | 
					      for (const expectedRepoInformationItem of expectedRepoInformation) {
 | 
				
			||||||
        if (!plugins.smartobject.smartGet(npmextraJson.gitzone, expectedRepoInformationItem)) {
 | 
					        if (
 | 
				
			||||||
 | 
					          !plugins.smartobject.smartGet(
 | 
				
			||||||
 | 
					            npmextraJson.gitzone,
 | 
				
			||||||
 | 
					            expectedRepoInformationItem,
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
          interactInstance.addQuestions([
 | 
					          interactInstance.addQuestions([
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              message: `What is the value of ${expectedRepoInformationItem}`,
 | 
					              message: `What is the value of ${expectedRepoInformationItem}`,
 | 
				
			||||||
@@ -43,7 +48,9 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      const answerbucket = await interactInstance.runQueue();
 | 
					      const answerbucket = await interactInstance.runQueue();
 | 
				
			||||||
      for (const expectedRepoInformationItem of expectedRepoInformation) {
 | 
					      for (const expectedRepoInformationItem of expectedRepoInformation) {
 | 
				
			||||||
        const cliProvidedValue = answerbucket.getAnswerFor(expectedRepoInformationItem);
 | 
					        const cliProvidedValue = answerbucket.getAnswerFor(
 | 
				
			||||||
 | 
					          expectedRepoInformationItem,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        if (cliProvidedValue) {
 | 
					        if (cliProvidedValue) {
 | 
				
			||||||
          plugins.smartobject.smartAdd(
 | 
					          plugins.smartobject.smartAdd(
 | 
				
			||||||
            npmextraJson.gitzone,
 | 
					            npmextraJson.gitzone,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,8 @@ const ensureDependency = async (
 | 
				
			|||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case 'include':
 | 
					      case 'include':
 | 
				
			||||||
        if (!packageJsonObjectArg[section][packageName]) {
 | 
					        if (!packageJsonObjectArg[section][packageName]) {
 | 
				
			||||||
          packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version;
 | 
					          packageJsonObjectArg[section][packageName] =
 | 
				
			||||||
 | 
					            version === 'latest' ? '^1.0.0' : version;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      case 'latest':
 | 
					      case 'latest':
 | 
				
			||||||
@@ -54,9 +55,13 @@ const ensureDependency = async (
 | 
				
			|||||||
          const latestVersion = packageInfo['dist-tags'].latest;
 | 
					          const latestVersion = packageInfo['dist-tags'].latest;
 | 
				
			||||||
          packageJsonObjectArg[section][packageName] = `^${latestVersion}`;
 | 
					          packageJsonObjectArg[section][packageName] = `^${latestVersion}`;
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
          logger.log('warn', `Could not fetch latest version for ${packageName}, using existing or default`);
 | 
					          logger.log(
 | 
				
			||||||
 | 
					            'warn',
 | 
				
			||||||
 | 
					            `Could not fetch latest version for ${packageName}, using existing or default`,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
          if (!packageJsonObjectArg[section][packageName]) {
 | 
					          if (!packageJsonObjectArg[section][packageName]) {
 | 
				
			||||||
            packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version;
 | 
					            packageJsonObjectArg[section][packageName] =
 | 
				
			||||||
 | 
					              version === 'latest' ? '^1.0.0' : version;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
@@ -78,10 +83,10 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
        type: 'git',
 | 
					        type: 'git',
 | 
				
			||||||
        url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}.git`,
 | 
					        url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}.git`,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      (packageJson.bugs = {
 | 
					      ((packageJson.bugs = {
 | 
				
			||||||
        url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}/issues`,
 | 
					        url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}/issues`,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
        (packageJson.homepage = `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}#readme`);
 | 
					        (packageJson.homepage = `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}#readme`));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Check for module type
 | 
					      // Check for module type
 | 
				
			||||||
      if (!packageJson.type) {
 | 
					      if (!packageJson.type) {
 | 
				
			||||||
@@ -91,9 +96,15 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // Check for private or public
 | 
					      // Check for private or public
 | 
				
			||||||
      if (packageJson.private !== undefined) {
 | 
					      if (packageJson.private !== undefined) {
 | 
				
			||||||
        logger.log('info', 'Success -> found private/public info in package.json!');
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'info',
 | 
				
			||||||
 | 
					          'Success -> found private/public info in package.json!',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        logger.log('error', 'found no private boolean! Setting it to private for now!');
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'error',
 | 
				
			||||||
 | 
					          'found no private boolean! Setting it to private for now!',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        packageJson.private = true;
 | 
					        packageJson.private = true;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -101,7 +112,10 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
      if (packageJson.license) {
 | 
					      if (packageJson.license) {
 | 
				
			||||||
        logger.log('info', 'Success -> found license in package.json!');
 | 
					        logger.log('info', 'Success -> found license in package.json!');
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        logger.log('error', 'found no license! Setting it to UNLICENSED for now!');
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'error',
 | 
				
			||||||
 | 
					          'found no license! Setting it to UNLICENSED for now!',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        packageJson.license = 'UNLICENSED';
 | 
					        packageJson.license = 'UNLICENSED';
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -109,13 +123,19 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
      if (packageJson.scripts.build) {
 | 
					      if (packageJson.scripts.build) {
 | 
				
			||||||
        logger.log('info', 'Success -> found build script in package.json!');
 | 
					        logger.log('info', 'Success -> found build script in package.json!');
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        logger.log('error', 'found no build script! Putting a placeholder there for now!');
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'error',
 | 
				
			||||||
 | 
					          'found no build script! Putting a placeholder there for now!',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        packageJson.scripts.build = `echo "Not needed for now"`;
 | 
					        packageJson.scripts.build = `echo "Not needed for now"`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Check for buildDocs script
 | 
					      // Check for buildDocs script
 | 
				
			||||||
      if (!packageJson.scripts.buildDocs) {
 | 
					      if (!packageJson.scripts.buildDocs) {
 | 
				
			||||||
        logger.log('info', 'found no buildDocs script! Putting tsdoc script there now.');
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'info',
 | 
				
			||||||
 | 
					          'found no buildDocs script! Putting tsdoc script there now.',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        packageJson.scripts.buildDocs = `tsdoc`;
 | 
					        packageJson.scripts.buildDocs = `tsdoc`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -134,9 +154,24 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
      ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // check for dependencies
 | 
					      // check for dependencies
 | 
				
			||||||
      await ensureDependency(packageJson, 'devDep', 'latest', '@push.rocks/tapbundle');
 | 
					      await ensureDependency(
 | 
				
			||||||
      await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tstest');
 | 
					        packageJson,
 | 
				
			||||||
      await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tsbuild');
 | 
					        'devDep',
 | 
				
			||||||
 | 
					        'latest',
 | 
				
			||||||
 | 
					        '@push.rocks/tapbundle',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await ensureDependency(
 | 
				
			||||||
 | 
					        packageJson,
 | 
				
			||||||
 | 
					        'devDep',
 | 
				
			||||||
 | 
					        'latest',
 | 
				
			||||||
 | 
					        '@git.zone/tstest',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await ensureDependency(
 | 
				
			||||||
 | 
					        packageJson,
 | 
				
			||||||
 | 
					        'devDep',
 | 
				
			||||||
 | 
					        'latest',
 | 
				
			||||||
 | 
					        '@git.zone/tsbuild',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // set overrides
 | 
					      // set overrides
 | 
				
			||||||
      const overrides = plugins.smartfile.fs.toObjectSync(
 | 
					      const overrides = plugins.smartfile.fs.toObjectSync(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,12 @@ const prettierDefaultMarkdownConfig: prettier.Options = {
 | 
				
			|||||||
  parser: 'markdown',
 | 
					  parser: 'markdown',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const filesToFormat = [`ts/**/*.ts`, `test/**/*.ts`, `readme.md`, `docs/**/*.md`];
 | 
					const filesToFormat = [
 | 
				
			||||||
 | 
					  `ts/**/*.ts`,
 | 
				
			||||||
 | 
					  `test/**/*.ts`,
 | 
				
			||||||
 | 
					  `readme.md`,
 | 
				
			||||||
 | 
					  `docs/**/*.md`,
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => {
 | 
					const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => {
 | 
				
			||||||
  switch (fileArg.parsedPath.ext) {
 | 
					  switch (fileArg.parsedPath.ext) {
 | 
				
			||||||
@@ -39,7 +44,10 @@ const prettierTypeScriptPipestop = plugins.through2.obj(
 | 
				
			|||||||
      cb(null);
 | 
					      cb(null);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      logger.log('info', `${fileArg.path} is being reformated!`);
 | 
					      logger.log('info', `${fileArg.path} is being reformated!`);
 | 
				
			||||||
      const formatedFileString = await prettier.format(fileString, chosenConfig);
 | 
					      const formatedFileString = await prettier.format(
 | 
				
			||||||
 | 
					        fileString,
 | 
				
			||||||
 | 
					        chosenConfig,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      fileArg.setContentsFromString(formatedFileString);
 | 
					      fileArg.setContentsFromString(formatedFileString);
 | 
				
			||||||
      cb(null, fileArg);
 | 
					      cb(null, fileArg);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,8 @@ export const run = async () => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Check and initialize readme.hints.md if it doesn't exist
 | 
					  // Check and initialize readme.hints.md if it doesn't exist
 | 
				
			||||||
  const readmeHintsExists = await plugins.smartfile.fs.fileExists(readmeHintsPath);
 | 
					  const readmeHintsExists =
 | 
				
			||||||
 | 
					    await plugins.smartfile.fs.fileExists(readmeHintsPath);
 | 
				
			||||||
  if (!readmeHintsExists) {
 | 
					  if (!readmeHintsExists) {
 | 
				
			||||||
    await plugins.smartfile.fs.toFs(
 | 
					    await plugins.smartfile.fs.toFs(
 | 
				
			||||||
      '# Project Readme Hints\n\nThis is the initial readme hints file.',
 | 
					      '# Project Readme Hints\n\nThis is the initial readme hints file.',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,10 +26,12 @@ export const run = async (project: Project) => {
 | 
				
			|||||||
    case 'npm':
 | 
					    case 'npm':
 | 
				
			||||||
    case 'wcc':
 | 
					    case 'wcc':
 | 
				
			||||||
      if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') {
 | 
					      if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') {
 | 
				
			||||||
        const ciTemplateDefault = await templateModule.getTemplate('ci_default');
 | 
					        const ciTemplateDefault =
 | 
				
			||||||
 | 
					          await templateModule.getTemplate('ci_default');
 | 
				
			||||||
        ciTemplateDefault.writeToDisk(paths.cwd);
 | 
					        ciTemplateDefault.writeToDisk(paths.cwd);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        const ciTemplateDefault = await templateModule.getTemplate('ci_default_private');
 | 
					        const ciTemplateDefault =
 | 
				
			||||||
 | 
					          await templateModule.getTemplate('ci_default_private');
 | 
				
			||||||
        ciTemplateDefault.writeToDisk(paths.cwd);
 | 
					        ciTemplateDefault.writeToDisk(paths.cwd);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      logger.log('info', 'Updated .gitlabci.yml!');
 | 
					      logger.log('info', 'Updated .gitlabci.yml!');
 | 
				
			||||||
@@ -41,7 +43,8 @@ export const run = async (project: Project) => {
 | 
				
			|||||||
      logger.log('info', 'Updated CI/CD config files!');
 | 
					      logger.log('info', 'Updated CI/CD config files!');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // lets care about docker
 | 
					      // lets care about docker
 | 
				
			||||||
      const dockerTemplate = await templateModule.getTemplate('dockerfile_service');
 | 
					      const dockerTemplate =
 | 
				
			||||||
 | 
					        await templateModule.getTemplate('dockerfile_service');
 | 
				
			||||||
      dockerTemplate.writeToDisk(paths.cwd);
 | 
					      dockerTemplate.writeToDisk(paths.cwd);
 | 
				
			||||||
      logger.log('info', 'Updated Dockerfile!');
 | 
					      logger.log('info', 'Updated Dockerfile!');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,17 +59,22 @@ export const run = async (project: Project) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // update html
 | 
					  // update html
 | 
				
			||||||
  if (project.gitzoneConfig.data.projectType === 'website') {
 | 
					  if (project.gitzoneConfig.data.projectType === 'website') {
 | 
				
			||||||
    const websiteUpdateTemplate = await templateModule.getTemplate('website_update');
 | 
					    const websiteUpdateTemplate =
 | 
				
			||||||
    const variables ={
 | 
					      await templateModule.getTemplate('website_update');
 | 
				
			||||||
 | 
					    const variables = {
 | 
				
			||||||
      assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl,
 | 
					      assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl,
 | 
				
			||||||
      legalUrl: project.gitzoneConfig.data.module.legalUrl,
 | 
					      legalUrl: project.gitzoneConfig.data.module.legalUrl,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    console.log('updating website template with variables\n', JSON.stringify(variables, null, 2));
 | 
					    console.log(
 | 
				
			||||||
 | 
					      'updating website template with variables\n',
 | 
				
			||||||
 | 
					      JSON.stringify(variables, null, 2),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    websiteUpdateTemplate.supplyVariables(variables);
 | 
					    websiteUpdateTemplate.supplyVariables(variables);
 | 
				
			||||||
    await websiteUpdateTemplate.writeToDisk(paths.cwd);
 | 
					    await websiteUpdateTemplate.writeToDisk(paths.cwd);
 | 
				
			||||||
    logger.log('info', `Updated html for website!`);
 | 
					    logger.log('info', `Updated html for website!`);
 | 
				
			||||||
  } else if (project.gitzoneConfig.data.projectType === 'service') {
 | 
					  } else if (project.gitzoneConfig.data.projectType === 'service') {
 | 
				
			||||||
    const websiteUpdateTemplate = await templateModule.getTemplate('service_update');
 | 
					    const websiteUpdateTemplate =
 | 
				
			||||||
 | 
					      await templateModule.getTemplate('service_update');
 | 
				
			||||||
    await websiteUpdateTemplate.writeToDisk(paths.cwd);
 | 
					    await websiteUpdateTemplate.writeToDisk(paths.cwd);
 | 
				
			||||||
    logger.log('info', `Updated html for element template!`);
 | 
					    logger.log('info', `Updated html for element template!`);
 | 
				
			||||||
  } else if (project.gitzoneConfig.data.projectType === 'wcc') {
 | 
					  } else if (project.gitzoneConfig.data.projectType === 'wcc') {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,8 +19,12 @@ export const run = async (projectArg: Project) => {
 | 
				
			|||||||
  const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
 | 
					  const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
 | 
				
			||||||
  for (const publishModule of Object.keys(publishModules)) {
 | 
					  for (const publishModule of Object.keys(publishModules)) {
 | 
				
			||||||
    const publishConfig = publishModules[publishModule];
 | 
					    const publishConfig = publishModules[publishModule];
 | 
				
			||||||
    tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [`./${publishModule}/index.js`];
 | 
					    tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [
 | 
				
			||||||
 | 
					      `./${publishModule}/index.js`,
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  tsconfigSmartfile.setContentsFromString(JSON.stringify(tsconfigObject, null, 2));
 | 
					  tsconfigSmartfile.setContentsFromString(
 | 
				
			||||||
 | 
					    JSON.stringify(tsconfigObject, null, 2),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  await tsconfigSmartfile.write();
 | 
					  await tsconfigSmartfile.write();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,12 @@ export class CleanupFormatter extends BaseFormatter {
 | 
				
			|||||||
    const changes: IPlannedChange[] = [];
 | 
					    const changes: IPlannedChange[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // List of files to remove
 | 
					    // List of files to remove
 | 
				
			||||||
    const filesToRemove = ['yarn.lock', 'package-lock.json', 'tslint.json', 'defaults.yml'];
 | 
					    const filesToRemove = [
 | 
				
			||||||
 | 
					      'yarn.lock',
 | 
				
			||||||
 | 
					      'package-lock.json',
 | 
				
			||||||
 | 
					      'tslint.json',
 | 
				
			||||||
 | 
					      'defaults.yml',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const file of filesToRemove) {
 | 
					    for (const file of filesToRemove) {
 | 
				
			||||||
      const exists = await plugins.smartfile.fs.fileExists(file);
 | 
					      const exists = await plugins.smartfile.fs.fileExists(file);
 | 
				
			||||||
@@ -21,7 +26,7 @@ export class CleanupFormatter extends BaseFormatter {
 | 
				
			|||||||
          type: 'delete',
 | 
					          type: 'delete',
 | 
				
			||||||
          path: file,
 | 
					          path: file,
 | 
				
			||||||
          module: this.name,
 | 
					          module: this.name,
 | 
				
			||||||
          description: `Remove obsolete file`
 | 
					          description: `Remove obsolete file`,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,12 @@ export class LegacyFormatter extends BaseFormatter {
 | 
				
			|||||||
  private moduleName: string;
 | 
					  private moduleName: string;
 | 
				
			||||||
  private formatModule: any;
 | 
					  private formatModule: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(context: any, project: Project, moduleName: string, formatModule: any) {
 | 
					  constructor(
 | 
				
			||||||
 | 
					    context: any,
 | 
				
			||||||
 | 
					    project: Project,
 | 
				
			||||||
 | 
					    moduleName: string,
 | 
				
			||||||
 | 
					    formatModule: any,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    super(context, project);
 | 
					    super(context, project);
 | 
				
			||||||
    this.moduleName = moduleName;
 | 
					    this.moduleName = moduleName;
 | 
				
			||||||
    this.formatModule = formatModule;
 | 
					    this.formatModule = formatModule;
 | 
				
			||||||
@@ -21,12 +26,14 @@ export class LegacyFormatter extends BaseFormatter {
 | 
				
			|||||||
  async analyze(): Promise<IPlannedChange[]> {
 | 
					  async analyze(): Promise<IPlannedChange[]> {
 | 
				
			||||||
    // For legacy modules, we can't easily predict changes
 | 
					    // For legacy modules, we can't easily predict changes
 | 
				
			||||||
    // So we'll return a generic change that indicates the module will run
 | 
					    // So we'll return a generic change that indicates the module will run
 | 
				
			||||||
    return [{
 | 
					    return [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
        type: 'modify',
 | 
					        type: 'modify',
 | 
				
			||||||
        path: '<various files>',
 | 
					        path: '<various files>',
 | 
				
			||||||
        module: this.name,
 | 
					        module: this.name,
 | 
				
			||||||
      description: `Run ${this.name} formatter`
 | 
					        description: `Run ${this.name} formatter`,
 | 
				
			||||||
    }];
 | 
					      },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async applyChange(change: IPlannedChange): Promise<void> {
 | 
					  async applyChange(change: IPlannedChange): Promise<void> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,15 +10,76 @@ export class PrettierFormatter extends BaseFormatter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  async analyze(): Promise<IPlannedChange[]> {
 | 
					  async analyze(): Promise<IPlannedChange[]> {
 | 
				
			||||||
    const changes: IPlannedChange[] = [];
 | 
					    const changes: IPlannedChange[] = [];
 | 
				
			||||||
    const globPattern = '**/*.{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}';
 | 
					
 | 
				
			||||||
 | 
					    // Define directories to format (TypeScript directories by default)
 | 
				
			||||||
 | 
					    const includeDirs = ['ts', 'ts_*', 'test', 'tests'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // File extensions to format
 | 
				
			||||||
 | 
					    const extensions = '{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Also format root-level config files
 | 
				
			||||||
 | 
					    const rootConfigFiles = [
 | 
				
			||||||
 | 
					      'package.json',
 | 
				
			||||||
 | 
					      'tsconfig.json',
 | 
				
			||||||
 | 
					      'npmextra.json',
 | 
				
			||||||
 | 
					      '.prettierrc',
 | 
				
			||||||
 | 
					      '.prettierrc.json',
 | 
				
			||||||
 | 
					      '.prettierrc.js',
 | 
				
			||||||
 | 
					      'readme.md',
 | 
				
			||||||
 | 
					      'README.md',
 | 
				
			||||||
 | 
					      'changelog.md',
 | 
				
			||||||
 | 
					      'CHANGELOG.md',
 | 
				
			||||||
 | 
					      // Skip files without extensions as prettier can't infer parser
 | 
				
			||||||
 | 
					      // 'license',
 | 
				
			||||||
 | 
					      // 'LICENSE',
 | 
				
			||||||
 | 
					      '*.md',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Collect all files to format
 | 
				
			||||||
 | 
					    const allFiles: string[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add files from TypeScript directories
 | 
				
			||||||
 | 
					    for (const dir of includeDirs) {
 | 
				
			||||||
 | 
					      const globPattern = `${dir}/**/*.${extensions}`;
 | 
				
			||||||
 | 
					      const dirFiles = await plugins.smartfile.fs.listFileTree(
 | 
				
			||||||
 | 
					        '.',
 | 
				
			||||||
 | 
					        globPattern,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      allFiles.push(...dirFiles);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add root config files
 | 
				
			||||||
 | 
					    for (const pattern of rootConfigFiles) {
 | 
				
			||||||
 | 
					      const rootFiles = await plugins.smartfile.fs.listFileTree('.', pattern);
 | 
				
			||||||
 | 
					      // Only include files at root level (no slashes in path)
 | 
				
			||||||
 | 
					      const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
 | 
				
			||||||
 | 
					      allFiles.push(...rootLevelFiles);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove duplicates
 | 
				
			||||||
 | 
					    const uniqueFiles = [...new Set(allFiles)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Get all files that match the pattern
 | 
					    // Get all files that match the pattern
 | 
				
			||||||
    const files = await plugins.smartfile.fs.listFileTree('.', globPattern);
 | 
					    const files = uniqueFiles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Ensure we only process actual files (not directories)
 | 
				
			||||||
 | 
					    const validFiles: string[] = [];
 | 
				
			||||||
 | 
					    for (const file of files) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const stats = await plugins.smartfile.fs.stat(file);
 | 
				
			||||||
 | 
					        if (!stats.isDirectory()) {
 | 
				
			||||||
 | 
					          validFiles.push(file);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        // Skip files that can't be accessed
 | 
				
			||||||
 | 
					        logVerbose(`Skipping ${file} - cannot access: ${error.message}`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Check which files need formatting
 | 
					    // Check which files need formatting
 | 
				
			||||||
    for (const file of files) {
 | 
					    for (const file of validFiles) {
 | 
				
			||||||
      // Skip files that haven't changed
 | 
					      // Skip files that haven't changed
 | 
				
			||||||
      if (!await this.shouldProcessFile(file)) {
 | 
					      if (!(await this.shouldProcessFile(file))) {
 | 
				
			||||||
        logVerbose(`Skipping ${file} - no changes detected`);
 | 
					        logVerbose(`Skipping ${file} - no changes detected`);
 | 
				
			||||||
        continue;
 | 
					        continue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -27,7 +88,7 @@ export class PrettierFormatter extends BaseFormatter {
 | 
				
			|||||||
        type: 'modify',
 | 
					        type: 'modify',
 | 
				
			||||||
        path: file,
 | 
					        path: file,
 | 
				
			||||||
        module: this.name,
 | 
					        module: this.name,
 | 
				
			||||||
        description: 'Format with Prettier'
 | 
					        description: 'Format with Prettier',
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,38 +103,31 @@ export class PrettierFormatter extends BaseFormatter {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await this.preExecute();
 | 
					      await this.preExecute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Batch process files
 | 
					      logVerbose(`Processing ${changes.length} files sequentially`);
 | 
				
			||||||
      const batchSize = 10; // Process 10 files at a time
 | 
					 | 
				
			||||||
      const batches: IPlannedChange[][] = [];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (let i = 0; i < changes.length; i += batchSize) {
 | 
					      // Process files sequentially to avoid prettier cache/state issues
 | 
				
			||||||
        batches.push(changes.slice(i, i + batchSize));
 | 
					      for (let i = 0; i < changes.length; i++) {
 | 
				
			||||||
      }
 | 
					        const change = changes[i];
 | 
				
			||||||
 | 
					        logVerbose(
 | 
				
			||||||
 | 
					          `Processing file ${i + 1}/${changes.length}: ${change.path}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      logVerbose(`Processing ${changes.length} files in ${batches.length} batches`);
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
      for (let i = 0; i < batches.length; i++) {
 | 
					 | 
				
			||||||
        const batch = batches[i];
 | 
					 | 
				
			||||||
        logVerbose(`Processing batch ${i + 1}/${batches.length} (${batch.length} files)`);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Process batch in parallel
 | 
					 | 
				
			||||||
        const promises = batch.map(async (change) => {
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          await this.applyChange(change);
 | 
					          await this.applyChange(change);
 | 
				
			||||||
          this.stats.recordFileOperation(this.name, change.type, true);
 | 
					          this.stats.recordFileOperation(this.name, change.type, true);
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
          this.stats.recordFileOperation(this.name, change.type, false);
 | 
					          this.stats.recordFileOperation(this.name, change.type, false);
 | 
				
			||||||
            logger.log('error', `Failed to format ${change.path}: ${error.message}`);
 | 
					          logger.log(
 | 
				
			||||||
 | 
					            'error',
 | 
				
			||||||
 | 
					            `Failed to format ${change.path}: ${error.message}`,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
          // Don't throw - continue with other files
 | 
					          // Don't throw - continue with other files
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        await Promise.all(promises);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await this.postExecute();
 | 
					      await this.postExecute();
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      await this.context.rollbackOperation();
 | 
					      // Rollback removed - no longer tracking operations
 | 
				
			||||||
      throw error;
 | 
					      throw error;
 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
      this.stats.endModule(this.name, startTime);
 | 
					      this.stats.endModule(this.name, startTime);
 | 
				
			||||||
@@ -84,27 +138,71 @@ export class PrettierFormatter extends BaseFormatter {
 | 
				
			|||||||
    if (change.type !== 'modify') return;
 | 
					    if (change.type !== 'modify') return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      // Validate the path before processing
 | 
				
			||||||
 | 
					      if (!change.path || change.path.trim() === '') {
 | 
				
			||||||
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'error',
 | 
				
			||||||
 | 
					          `Invalid empty path in change: ${JSON.stringify(change)}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        throw new Error('Invalid empty path');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Read current content
 | 
					      // Read current content
 | 
				
			||||||
      const content = await plugins.smartfile.fs.toStringSync(change.path);
 | 
					      const content = plugins.smartfile.fs.toStringSync(change.path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Format with prettier
 | 
					      // Format with prettier
 | 
				
			||||||
      const prettier = await import('prettier');
 | 
					      const prettier = await import('prettier');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Skip files that prettier can't parse without explicit parser
 | 
				
			||||||
 | 
					      const fileExt = plugins.path.extname(change.path).toLowerCase();
 | 
				
			||||||
 | 
					      if (!fileExt || fileExt === '') {
 | 
				
			||||||
 | 
					        // Files without extensions need explicit parser
 | 
				
			||||||
 | 
					        logVerbose(
 | 
				
			||||||
 | 
					          `Skipping ${change.path} - no file extension for parser inference`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
        const formatted = await prettier.format(content, {
 | 
					        const formatted = await prettier.format(content, {
 | 
				
			||||||
          filepath: change.path,
 | 
					          filepath: change.path,
 | 
				
			||||||
        ...(await this.getPrettierConfig())
 | 
					          ...(await this.getPrettierConfig()),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Only write if content actually changed
 | 
					        // Only write if content actually changed
 | 
				
			||||||
        if (formatted !== content) {
 | 
					        if (formatted !== content) {
 | 
				
			||||||
 | 
					          // Debug: log the path being written
 | 
				
			||||||
 | 
					          logVerbose(`Writing formatted content to: ${change.path}`);
 | 
				
			||||||
          await this.modifyFile(change.path, formatted);
 | 
					          await this.modifyFile(change.path, formatted);
 | 
				
			||||||
          logVerbose(`Formatted ${change.path}`);
 | 
					          logVerbose(`Formatted ${change.path}`);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
        // Still update cache even if content didn't change
 | 
					 | 
				
			||||||
        await this.cache.updateFileCache(change.path);
 | 
					 | 
				
			||||||
          logVerbose(`No formatting changes for ${change.path}`);
 | 
					          logVerbose(`No formatting changes for ${change.path}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (prettierError) {
 | 
				
			||||||
 | 
					        // Check if it's a parser error
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          prettierError.message &&
 | 
				
			||||||
 | 
					          prettierError.message.includes('No parser could be inferred')
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          logVerbose(`Skipping ${change.path} - ${prettierError.message}`);
 | 
				
			||||||
 | 
					          return; // Skip this file silently
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw prettierError;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      logger.log('error', `Failed to format ${change.path}: ${error.message}`);
 | 
					      // Log the full error stack for debugging mkdir issues
 | 
				
			||||||
 | 
					      if (error.message && error.message.includes('mkdir')) {
 | 
				
			||||||
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'error',
 | 
				
			||||||
 | 
					          `Failed to format ${change.path}: ${error.message}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        logger.log('error', `Error stack: ${error.stack}`);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        logger.log(
 | 
				
			||||||
 | 
					          'error',
 | 
				
			||||||
 | 
					          `Failed to format ${change.path}: ${error.message}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      throw error;
 | 
					      throw error;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -119,7 +217,7 @@ export class PrettierFormatter extends BaseFormatter {
 | 
				
			|||||||
      printWidth: 80,
 | 
					      printWidth: 80,
 | 
				
			||||||
      tabWidth: 2,
 | 
					      tabWidth: 2,
 | 
				
			||||||
      semi: true,
 | 
					      semi: true,
 | 
				
			||||||
      arrowParens: 'always'
 | 
					      arrowParens: 'always',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -8,12 +8,14 @@ export class ReadmeFormatter extends BaseFormatter {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async analyze(): Promise<IPlannedChange[]> {
 | 
					  async analyze(): Promise<IPlannedChange[]> {
 | 
				
			||||||
    return [{
 | 
					    return [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
        type: 'modify',
 | 
					        type: 'modify',
 | 
				
			||||||
        path: 'readme.md',
 | 
					        path: 'readme.md',
 | 
				
			||||||
        module: this.name,
 | 
					        module: this.name,
 | 
				
			||||||
      description: 'Ensure readme files exist'
 | 
					        description: 'Ensure readme files exist',
 | 
				
			||||||
    }];
 | 
					      },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async applyChange(change: IPlannedChange): Promise<void> {
 | 
					  async applyChange(change: IPlannedChange): Promise<void> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,8 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js';
 | 
				
			|||||||
import { ReadmeFormatter } from './formatters/readme.formatter.js';
 | 
					import { ReadmeFormatter } from './formatters/readme.formatter.js';
 | 
				
			||||||
import { CopyFormatter } from './formatters/copy.formatter.js';
 | 
					import { CopyFormatter } from './formatters/copy.formatter.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export let run = async (options: {
 | 
					export let run = async (
 | 
				
			||||||
 | 
					  options: {
 | 
				
			||||||
    dryRun?: boolean;
 | 
					    dryRun?: boolean;
 | 
				
			||||||
    yes?: boolean;
 | 
					    yes?: boolean;
 | 
				
			||||||
    planOnly?: boolean;
 | 
					    planOnly?: boolean;
 | 
				
			||||||
@@ -26,7 +27,8 @@ export let run = async (options: {
 | 
				
			|||||||
    interactive?: boolean;
 | 
					    interactive?: boolean;
 | 
				
			||||||
    parallel?: boolean;
 | 
					    parallel?: boolean;
 | 
				
			||||||
    verbose?: boolean;
 | 
					    verbose?: boolean;
 | 
				
			||||||
} = {}): Promise<any> => {
 | 
					  } = {},
 | 
				
			||||||
 | 
					): Promise<any> => {
 | 
				
			||||||
  // Set verbose mode if requested
 | 
					  // Set verbose mode if requested
 | 
				
			||||||
  if (options.verbose) {
 | 
					  if (options.verbose) {
 | 
				
			||||||
    setVerboseMode(true);
 | 
					    setVerboseMode(true);
 | 
				
			||||||
@@ -34,7 +36,7 @@ export let run = async (options: {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const project = await Project.fromCwd();
 | 
					  const project = await Project.fromCwd();
 | 
				
			||||||
  const context = new FormatContext();
 | 
					  const context = new FormatContext();
 | 
				
			||||||
  await context.initializeCache();  // Initialize the cache system
 | 
					  // Cache system removed - no longer needed
 | 
				
			||||||
  const planner = new FormatPlanner();
 | 
					  const planner = new FormatPlanner();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Get configuration from npmextra
 | 
					  // Get configuration from npmextra
 | 
				
			||||||
@@ -49,24 +51,21 @@ export let run = async (options: {
 | 
				
			|||||||
      autoRollbackOnError: true,
 | 
					      autoRollbackOnError: true,
 | 
				
			||||||
      backupRetentionDays: 7,
 | 
					      backupRetentionDays: 7,
 | 
				
			||||||
      maxBackupSize: '100MB',
 | 
					      maxBackupSize: '100MB',
 | 
				
			||||||
      excludePatterns: ['node_modules/**', '.git/**']
 | 
					      excludePatterns: ['node_modules/**', '.git/**'],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    modules: {
 | 
					    modules: {
 | 
				
			||||||
      skip: [],
 | 
					      skip: [],
 | 
				
			||||||
      only: [],
 | 
					      only: [],
 | 
				
			||||||
      order: []
 | 
					      order: [],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    parallel: true,
 | 
					    parallel: true,
 | 
				
			||||||
    cache: {
 | 
					    cache: {
 | 
				
			||||||
      enabled: true,
 | 
					      enabled: true,
 | 
				
			||||||
      clean: true  // Clean invalid entries from cache
 | 
					      clean: true, // Clean invalid entries from cache
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Clean cache if configured
 | 
					  // Cache cleaning removed - no longer using cache system
 | 
				
			||||||
  if (formatConfig.cache.clean) {
 | 
					 | 
				
			||||||
    await context.getChangeCache().clean();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Override config with command options
 | 
					  // Override config with command options
 | 
				
			||||||
  const interactive = options.interactive ?? formatConfig.interactive;
 | 
					  const interactive = options.interactive ?? formatConfig.interactive;
 | 
				
			||||||
@@ -89,7 +88,7 @@ export let run = async (options: {
 | 
				
			|||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Filter formatters based on configuration
 | 
					    // Filter formatters based on configuration
 | 
				
			||||||
    const activeFormatters = formatters.filter(formatter => {
 | 
					    const activeFormatters = formatters.filter((formatter) => {
 | 
				
			||||||
      if (formatConfig.modules.only.length > 0) {
 | 
					      if (formatConfig.modules.only.length > 0) {
 | 
				
			||||||
        return formatConfig.modules.only.includes(formatter.name);
 | 
					        return formatConfig.modules.only.includes(formatter.name);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -110,7 +109,10 @@ export let run = async (options: {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Save plan if requested
 | 
					    // Save plan if requested
 | 
				
			||||||
    if (options.savePlan) {
 | 
					    if (options.savePlan) {
 | 
				
			||||||
      await plugins.smartfile.memory.toFs(JSON.stringify(plan, null, 2), options.savePlan);
 | 
					      await plugins.smartfile.memory.toFs(
 | 
				
			||||||
 | 
					        JSON.stringify(plan, null, 2),
 | 
				
			||||||
 | 
					        options.savePlan,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      logger.log('info', `Plan saved to ${options.savePlan}`);
 | 
					      logger.log('info', `Plan saved to ${options.savePlan}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -132,7 +134,7 @@ export let run = async (options: {
 | 
				
			|||||||
        type: 'confirm',
 | 
					        type: 'confirm',
 | 
				
			||||||
        name: 'proceed',
 | 
					        name: 'proceed',
 | 
				
			||||||
        message: 'Proceed with formatting?',
 | 
					        message: 'Proceed with formatting?',
 | 
				
			||||||
        default: true
 | 
					        default: true,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!(response as any).value) {
 | 
					      if (!(response as any).value) {
 | 
				
			||||||
@@ -142,7 +144,10 @@ export let run = async (options: {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Execute phase
 | 
					    // Execute phase
 | 
				
			||||||
    logger.log('info', `Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`);
 | 
					    logger.log(
 | 
				
			||||||
 | 
					      'info',
 | 
				
			||||||
 | 
					      `Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    await planner.executePlan(plan, activeFormatters, context, parallel);
 | 
					    await planner.executePlan(plan, activeFormatters, context, parallel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Finish statistics tracking
 | 
					    // Finish statistics tracking
 | 
				
			||||||
@@ -161,20 +166,10 @@ export let run = async (options: {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.log('success', 'Format operations completed successfully!');
 | 
					    logger.log('success', 'Format operations completed successfully!');
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    logger.log('error', `Format operation failed: ${error.message}`);
 | 
					    logger.log('error', `Format operation failed: ${error.message}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Automatic rollback if enabled
 | 
					    // Rollback system has been removed for stability
 | 
				
			||||||
    if (formatConfig.rollback.enabled && formatConfig.rollback.autoRollbackOnError) {
 | 
					 | 
				
			||||||
      logger.log('info', 'Attempting automatic rollback...');
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        await context.rollbackOperation();
 | 
					 | 
				
			||||||
        logger.log('success', 'Rollback completed successfully');
 | 
					 | 
				
			||||||
      } catch (rollbackError) {
 | 
					 | 
				
			||||||
        logger.log('error', `Rollback failed: ${rollbackError.message}`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    throw error;
 | 
					    throw error;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -182,67 +177,16 @@ export let run = async (options: {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Export CLI command handlers
 | 
					// Export CLI command handlers
 | 
				
			||||||
export const handleRollback = async (operationId?: string): Promise<void> => {
 | 
					export const handleRollback = async (operationId?: string): Promise<void> => {
 | 
				
			||||||
  const context = new FormatContext();
 | 
					  logger.log('info', 'Rollback system has been disabled for stability');
 | 
				
			||||||
  const rollbackManager = context.getRollbackManager();
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  if (!operationId) {
 | 
					 | 
				
			||||||
    // Rollback to last operation
 | 
					 | 
				
			||||||
    const backups = await rollbackManager.listBackups();
 | 
					 | 
				
			||||||
    const lastOperation = backups
 | 
					 | 
				
			||||||
      .filter(op => op.status !== 'rolled-back')
 | 
					 | 
				
			||||||
      .sort((a, b) => b.timestamp - a.timestamp)[0];
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if (!lastOperation) {
 | 
					 | 
				
			||||||
      logger.log('warn', 'No operations available for rollback');
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    operationId = lastOperation.id;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    await rollbackManager.rollback(operationId);
 | 
					 | 
				
			||||||
    logger.log('success', `Successfully rolled back operation ${operationId}`);
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    logger.log('error', `Rollback failed: ${error.message}`);
 | 
					 | 
				
			||||||
    throw error;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const handleListBackups = async (): Promise<void> => {
 | 
					export const handleListBackups = async (): Promise<void> => {
 | 
				
			||||||
  const context = new FormatContext();
 | 
					  logger.log('info', 'Backup system has been disabled for stability');
 | 
				
			||||||
  const rollbackManager = context.getRollbackManager();
 | 
					 | 
				
			||||||
  const backups = await rollbackManager.listBackups();
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  if (backups.length === 0) {
 | 
					 | 
				
			||||||
    logger.log('info', 'No backup operations found');
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  console.log('\nAvailable backups:');
 | 
					 | 
				
			||||||
  console.log('━'.repeat(50));
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  for (const backup of backups) {
 | 
					 | 
				
			||||||
    const date = new Date(backup.timestamp).toLocaleString();
 | 
					 | 
				
			||||||
    const status = backup.status;
 | 
					 | 
				
			||||||
    const filesCount = backup.files.length;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    console.log(`ID: ${backup.id}`);
 | 
					 | 
				
			||||||
    console.log(`Date: ${date}`);
 | 
					 | 
				
			||||||
    console.log(`Status: ${status}`);
 | 
					 | 
				
			||||||
    console.log(`Files: ${filesCount}`);
 | 
					 | 
				
			||||||
    console.log('─'.repeat(50));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const handleCleanBackups = async (): Promise<void> => {
 | 
					export const handleCleanBackups = async (): Promise<void> => {
 | 
				
			||||||
  const context = new FormatContext();
 | 
					  logger.log(
 | 
				
			||||||
  const rollbackManager = context.getRollbackManager();
 | 
					    'info',
 | 
				
			||||||
  
 | 
					    'Backup cleaning has been disabled - backup system removed',
 | 
				
			||||||
  // Get retention days from config
 | 
					  );
 | 
				
			||||||
  const npmextraConfig = new plugins.npmextra.Npmextra();
 | 
					 | 
				
			||||||
  const retentionDays = npmextraConfig.dataFor<any>('gitzone.format.rollback.backupRetentionDays', 7);
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  await rollbackManager.cleanOldBackups(retentionDays);
 | 
					 | 
				
			||||||
  logger.log('success', `Cleaned backups older than ${retentionDays} days`);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -9,7 +9,7 @@ export type IFormatOperation = {
 | 
				
			|||||||
  }>;
 | 
					  }>;
 | 
				
			||||||
  status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back';
 | 
					  status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back';
 | 
				
			||||||
  error?: Error;
 | 
					  error?: Error;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type IFormatPlan = {
 | 
					export type IFormatPlan = {
 | 
				
			||||||
  summary: {
 | 
					  summary: {
 | 
				
			||||||
@@ -32,7 +32,7 @@ export type IFormatPlan = {
 | 
				
			|||||||
    message: string;
 | 
					    message: string;
 | 
				
			||||||
    module: string;
 | 
					    module: string;
 | 
				
			||||||
  }>;
 | 
					  }>;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type IPlannedChange = {
 | 
					export type IPlannedChange = {
 | 
				
			||||||
  type: 'create' | 'modify' | 'delete';
 | 
					  type: 'create' | 'modify' | 'delete';
 | 
				
			||||||
@@ -42,4 +42,4 @@ export type IPlannedChange = {
 | 
				
			|||||||
  content?: string; // For create/modify operations
 | 
					  content?: string; // For create/modify operations
 | 
				
			||||||
  diff?: string;
 | 
					  diff?: string;
 | 
				
			||||||
  size?: number;
 | 
					  size?: number;
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,10 @@ export class Meta {
 | 
				
			|||||||
   * sorts the metaRepoData
 | 
					   * sorts the metaRepoData
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async sortMetaRepoData() {
 | 
					  public async sortMetaRepoData() {
 | 
				
			||||||
    const stringifiedMetadata = plugins.smartjson.stringify(this.metaRepoData, []);
 | 
					    const stringifiedMetadata = plugins.smartjson.stringify(
 | 
				
			||||||
 | 
					      this.metaRepoData,
 | 
				
			||||||
 | 
					      [],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata);
 | 
					    this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,11 +48,15 @@ export class Meta {
 | 
				
			|||||||
  public async readDirectory() {
 | 
					  public async readDirectory() {
 | 
				
			||||||
    await this.syncToRemote(true);
 | 
					    await this.syncToRemote(true);
 | 
				
			||||||
    logger.log('info', `reading directory`);
 | 
					    logger.log('info', `reading directory`);
 | 
				
			||||||
    const metaFileExists = plugins.smartfile.fs.fileExistsSync(this.filePaths.metaJson);
 | 
					    const metaFileExists = plugins.smartfile.fs.fileExistsSync(
 | 
				
			||||||
 | 
					      this.filePaths.metaJson,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    if (!metaFileExists) {
 | 
					    if (!metaFileExists) {
 | 
				
			||||||
      throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`);
 | 
					      throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.metaRepoData = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson);
 | 
					    this.metaRepoData = plugins.smartfile.fs.toObjectSync(
 | 
				
			||||||
 | 
					      this.filePaths.metaJson,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -76,7 +83,10 @@ export class Meta {
 | 
				
			|||||||
      this.filePaths.metaJson,
 | 
					      this.filePaths.metaJson,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    // write .gitignore to disk
 | 
					    // write .gitignore to disk
 | 
				
			||||||
    plugins.smartfile.memory.toFsSync(await this.generateGitignore(), this.filePaths.gitIgnore);
 | 
					    plugins.smartfile.memory.toFsSync(
 | 
				
			||||||
 | 
					      await this.generateGitignore(),
 | 
				
			||||||
 | 
					      this.filePaths.gitIgnore,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -84,13 +94,17 @@ export class Meta {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  public async syncToRemote(gitCleanArg = false) {
 | 
					  public async syncToRemote(gitCleanArg = false) {
 | 
				
			||||||
    logger.log('info', `syncing from origin master`);
 | 
					    logger.log('info', `syncing from origin master`);
 | 
				
			||||||
    await this.smartshellInstance.exec(`cd ${this.cwd} && git pull origin master`);
 | 
					    await this.smartshellInstance.exec(
 | 
				
			||||||
 | 
					      `cd ${this.cwd} && git pull origin master`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    if (gitCleanArg) {
 | 
					    if (gitCleanArg) {
 | 
				
			||||||
      logger.log('info', `cleaning the repository from old directories`);
 | 
					      logger.log('info', `cleaning the repository from old directories`);
 | 
				
			||||||
      await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`);
 | 
					      await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    logger.log('info', `syncing  to remote origin master`);
 | 
					    logger.log('info', `syncing  to remote origin master`);
 | 
				
			||||||
    await this.smartshellInstance.exec(`cd ${this.cwd} && git push origin master`);
 | 
					    await this.smartshellInstance.exec(
 | 
				
			||||||
 | 
					      `cd ${this.cwd} && git push origin master`,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -98,7 +112,9 @@ export class Meta {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  public async updateLocalRepos() {
 | 
					  public async updateLocalRepos() {
 | 
				
			||||||
    await this.syncToRemote();
 | 
					    await this.syncToRemote();
 | 
				
			||||||
    const projects = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson).projects;
 | 
					    const projects = plugins.smartfile.fs.toObjectSync(
 | 
				
			||||||
 | 
					      this.filePaths.metaJson,
 | 
				
			||||||
 | 
					    ).projects;
 | 
				
			||||||
    const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd);
 | 
					    const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd);
 | 
				
			||||||
    for (const preExistingFolderArg of preExistingFolders) {
 | 
					    for (const preExistingFolderArg of preExistingFolders) {
 | 
				
			||||||
      if (
 | 
					      if (
 | 
				
			||||||
@@ -107,14 +123,18 @@ export class Meta {
 | 
				
			|||||||
          projectFolder.startsWith(preExistingFolderArg),
 | 
					          projectFolder.startsWith(preExistingFolderArg),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        const response = await plugins.smartinteraction.SmartInteract.getCliConfirmation(
 | 
					        const response =
 | 
				
			||||||
 | 
					          await plugins.smartinteraction.SmartInteract.getCliConfirmation(
 | 
				
			||||||
            `Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`,
 | 
					            `Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`,
 | 
				
			||||||
            true,
 | 
					            true,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        if (response) {
 | 
					        if (response) {
 | 
				
			||||||
          logger.log('warn', `Deleting >>${preExistingFolderArg}<<!`);
 | 
					          logger.log('warn', `Deleting >>${preExistingFolderArg}<<!`);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          logger.log('warn', `Not deleting ${preExistingFolderArg} by request!`);
 | 
					          logger.log(
 | 
				
			||||||
 | 
					            'warn',
 | 
				
			||||||
 | 
					            `Not deleting ${preExistingFolderArg} by request!`,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -160,7 +180,9 @@ export class Meta {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  public async initProject() {
 | 
					  public async initProject() {
 | 
				
			||||||
    await this.syncToRemote(true);
 | 
					    await this.syncToRemote(true);
 | 
				
			||||||
    const fileExists = await plugins.smartfile.fs.fileExists(this.filePaths.metaJson);
 | 
					    const fileExists = await plugins.smartfile.fs.fileExists(
 | 
				
			||||||
 | 
					      this.filePaths.metaJson,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    if (!fileExists) {
 | 
					    if (!fileExists) {
 | 
				
			||||||
      await plugins.smartfile.memory.toFs(
 | 
					      await plugins.smartfile.memory.toFs(
 | 
				
			||||||
        JSON.stringify({
 | 
					        JSON.stringify({
 | 
				
			||||||
@@ -168,7 +190,10 @@ export class Meta {
 | 
				
			|||||||
        }),
 | 
					        }),
 | 
				
			||||||
        this.filePaths.metaJson,
 | 
					        this.filePaths.metaJson,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      logger.log(`success`, `created a new .meta.json in directory ${this.cwd}`);
 | 
					      logger.log(
 | 
				
			||||||
 | 
					        `success`,
 | 
				
			||||||
 | 
					        `created a new .meta.json in directory ${this.cwd}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      await plugins.smartfile.memory.toFs(
 | 
					      await plugins.smartfile.memory.toFs(
 | 
				
			||||||
        JSON.stringify({
 | 
					        JSON.stringify({
 | 
				
			||||||
          name: this.dirName,
 | 
					          name: this.dirName,
 | 
				
			||||||
@@ -176,9 +201,15 @@ export class Meta {
 | 
				
			|||||||
        }),
 | 
					        }),
 | 
				
			||||||
        this.filePaths.packageJson,
 | 
					        this.filePaths.packageJson,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      logger.log(`success`, `created a new package.json in directory ${this.cwd}`);
 | 
					      logger.log(
 | 
				
			||||||
 | 
					        `success`,
 | 
				
			||||||
 | 
					        `created a new package.json in directory ${this.cwd}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      logger.log(`error`, `directory ${this.cwd} already has a .metaJson file. Doing nothing.`);
 | 
					      logger.log(
 | 
				
			||||||
 | 
					        `error`,
 | 
				
			||||||
 | 
					        `directory ${this.cwd} already has a .metaJson file. Doing nothing.`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await this.smartshellInstance.exec(
 | 
					    await this.smartshellInstance.exec(
 | 
				
			||||||
      `cd ${this.cwd} && git add -A && git commit -m "feat(project): init meta project for ${this.dirName}"`,
 | 
					      `cd ${this.cwd} && git add -A && git commit -m "feat(project): init meta project for ${this.dirName}"`,
 | 
				
			||||||
@@ -195,7 +226,9 @@ export class Meta {
 | 
				
			|||||||
    const existingProject = this.metaRepoData.projects[projectNameArg];
 | 
					    const existingProject = this.metaRepoData.projects[projectNameArg];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (existingProject) {
 | 
					    if (existingProject) {
 | 
				
			||||||
      throw new Error('Project already exists! Please remove it first before adding it again.');
 | 
					      throw new Error(
 | 
				
			||||||
 | 
					        'Project already exists! Please remove it first before adding it again.',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.metaRepoData.projects[projectNameArg] = gitUrlArg;
 | 
					    this.metaRepoData.projects[projectNameArg] = gitUrlArg;
 | 
				
			||||||
@@ -217,7 +250,10 @@ export class Meta {
 | 
				
			|||||||
    const existingProject = this.metaRepoData.projects[projectNameArg];
 | 
					    const existingProject = this.metaRepoData.projects[projectNameArg];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!existingProject) {
 | 
					    if (!existingProject) {
 | 
				
			||||||
      logger.log('error', `Project ${projectNameArg} does not exist! So it cannot be removed`);
 | 
					      logger.log(
 | 
				
			||||||
 | 
					        'error',
 | 
				
			||||||
 | 
					        `Project ${projectNameArg} does not exist! So it cannot be removed`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -228,7 +264,9 @@ export class Meta {
 | 
				
			|||||||
    await this.writeToDisk();
 | 
					    await this.writeToDisk();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.log('info', 'removing directory from cwd');
 | 
					    logger.log('info', 'removing directory from cwd');
 | 
				
			||||||
    await plugins.smartfile.fs.remove(plugins.path.join(paths.cwd, projectNameArg));
 | 
					    await plugins.smartfile.fs.remove(
 | 
				
			||||||
 | 
					      plugins.path.join(paths.cwd, projectNameArg),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    await this.updateLocalRepos();
 | 
					    await this.updateLocalRepos();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										261
									
								
								ts/mod_services/classes.dockercontainer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								ts/mod_services/classes.dockercontainer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,261 @@
 | 
				
			|||||||
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
 | 
					import * as helpers from './helpers.js';
 | 
				
			||||||
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ContainerStatus = 'running' | 'stopped' | 'not_exists';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IDockerRunOptions {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  image: string;
 | 
				
			||||||
 | 
					  ports?: { [key: string]: string };
 | 
				
			||||||
 | 
					  volumes?: { [key: string]: string };
 | 
				
			||||||
 | 
					  environment?: { [key: string]: string };
 | 
				
			||||||
 | 
					  restart?: string;
 | 
				
			||||||
 | 
					  command?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class DockerContainer {
 | 
				
			||||||
 | 
					  private smartshell: plugins.smartshell.Smartshell;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    this.smartshell = new plugins.smartshell.Smartshell({
 | 
				
			||||||
 | 
					      executor: 'bash',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Check if Docker is installed and available
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async checkDocker(): Promise<boolean> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await this.smartshell.exec('docker --version');
 | 
				
			||||||
 | 
					      return result.exitCode === 0;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get container status
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async getStatus(containerName: string): Promise<ContainerStatus> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      // Check if running
 | 
				
			||||||
 | 
					      const runningResult = await this.smartshell.exec(
 | 
				
			||||||
 | 
					        `docker ps --format '{{.Names}}' | grep -q "^${containerName}$"`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (runningResult.exitCode === 0) {
 | 
				
			||||||
 | 
					        return 'running';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      // Check if exists but stopped
 | 
				
			||||||
 | 
					      const existsResult = await this.smartshell.exec(
 | 
				
			||||||
 | 
					        `docker ps -a --format '{{.Names}}' | grep -q "^${containerName}$"`
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (existsResult.exitCode === 0) {
 | 
				
			||||||
 | 
					        return 'stopped';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      return 'not_exists';
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return 'not_exists';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Start a container
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async start(containerName: string): Promise<boolean> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await this.smartshell.exec(`docker start ${containerName}`);
 | 
				
			||||||
 | 
					      return result.exitCode === 0;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Stop a container
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async stop(containerName: string): Promise<boolean> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await this.smartshell.exec(`docker stop ${containerName}`);
 | 
				
			||||||
 | 
					      return result.exitCode === 0;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Remove a container
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async remove(containerName: string, force: boolean = false): Promise<boolean> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const forceFlag = force ? '-f' : '';
 | 
				
			||||||
 | 
					      const result = await this.smartshell.exec(`docker rm ${forceFlag} ${containerName}`);
 | 
				
			||||||
 | 
					      return result.exitCode === 0;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Run a new container
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async run(options: IDockerRunOptions): Promise<boolean> {
 | 
				
			||||||
 | 
					    let command = 'docker run -d';
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Add name
 | 
				
			||||||
 | 
					    command += ` --name ${options.name}`;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Add ports
 | 
				
			||||||
 | 
					    if (options.ports) {
 | 
				
			||||||
 | 
					      for (const [hostPort, containerPort] of Object.entries(options.ports)) {
 | 
				
			||||||
 | 
					        command += ` -p ${hostPort}:${containerPort}`;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Add volumes
 | 
				
			||||||
 | 
					    if (options.volumes) {
 | 
				
			||||||
 | 
					      for (const [hostPath, containerPath] of Object.entries(options.volumes)) {
 | 
				
			||||||
 | 
					        command += ` -v "${hostPath}:${containerPath}"`;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Add environment variables
 | 
				
			||||||
 | 
					    if (options.environment) {
 | 
				
			||||||
 | 
					      for (const [key, value] of Object.entries(options.environment)) {
 | 
				
			||||||
 | 
					        command += ` -e ${key}="${value}"`;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Add restart policy
 | 
				
			||||||
 | 
					    if (options.restart) {
 | 
				
			||||||
 | 
					      command += ` --restart ${options.restart}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Add image
 | 
				
			||||||
 | 
					    command += ` ${options.image}`;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Add command if provided
 | 
				
			||||||
 | 
					    if (options.command) {
 | 
				
			||||||
 | 
					      command += ` ${options.command}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await this.smartshell.exec(command);
 | 
				
			||||||
 | 
					      return result.exitCode === 0;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      logger.log('error', `Failed to run container: ${error.message}`);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Execute a command in a running container
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async exec(containerName: string, command: string): Promise<string> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await this.smartshell.exec(`docker exec ${containerName} ${command}`);
 | 
				
			||||||
 | 
					      if (result.exitCode === 0) {
 | 
				
			||||||
 | 
					        return result.stdout;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return '';
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return '';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get container logs
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async logs(containerName: string, lines?: number): Promise<string> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const tailFlag = lines ? `--tail ${lines}` : '';
 | 
				
			||||||
 | 
					      const result = await this.smartshell.exec(`docker logs ${tailFlag} ${containerName}`);
 | 
				
			||||||
 | 
					      return result.stdout;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return `Error getting logs: ${error.message}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Check if a container exists
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async exists(containerName: string): Promise<boolean> {
 | 
				
			||||||
 | 
					    const status = await this.getStatus(containerName);
 | 
				
			||||||
 | 
					    return status !== 'not_exists';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Check if a container is running
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async isRunning(containerName: string): Promise<boolean> {
 | 
				
			||||||
 | 
					    const status = await this.getStatus(containerName);
 | 
				
			||||||
 | 
					    return status === 'running';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Wait for a container to be ready
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async waitForReady(containerName: string, maxAttempts: number = 30): Promise<boolean> {
 | 
				
			||||||
 | 
					    for (let i = 0; i < maxAttempts; i++) {
 | 
				
			||||||
 | 
					      if (await this.isRunning(containerName)) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      await plugins.smartdelay.delayFor(1000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get container information
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async inspect(containerName: string): Promise<any> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const result = await this.smartshell.execSilent(`docker inspect ${containerName}`);
 | 
				
			||||||
 | 
					      if (result.exitCode === 0) {
 | 
				
			||||||
 | 
					        return JSON.parse(result.stdout);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get port mappings for a container
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async getPortMappings(containerName: string): Promise<{ [key: string]: string } | null> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      // Use docker inspect without format to get full JSON, then extract PortBindings
 | 
				
			||||||
 | 
					      const result = await this.smartshell.execSilent(`docker inspect ${containerName}`);
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (result.exitCode === 0 && result.stdout) {
 | 
				
			||||||
 | 
					        const inspectData = JSON.parse(result.stdout);
 | 
				
			||||||
 | 
					        if (inspectData && inspectData[0] && inspectData[0].HostConfig && inspectData[0].HostConfig.PortBindings) {
 | 
				
			||||||
 | 
					          const portBindings = inspectData[0].HostConfig.PortBindings;
 | 
				
			||||||
 | 
					          const mappings: { [key: string]: string } = {};
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          // Convert Docker's port binding format to simple host:container mapping
 | 
				
			||||||
 | 
					          for (const [containerPort, hostBindings] of Object.entries(portBindings)) {
 | 
				
			||||||
 | 
					            if (Array.isArray(hostBindings) && hostBindings.length > 0) {
 | 
				
			||||||
 | 
					              const hostPort = (hostBindings[0] as any).HostPort;
 | 
				
			||||||
 | 
					              if (hostPort) {
 | 
				
			||||||
 | 
					                mappings[containerPort.replace('/tcp', '').replace('/udp', '')] = hostPort;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          return mappings;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      // Silently fail - container might not exist
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										432
									
								
								ts/mod_services/classes.serviceconfiguration.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										432
									
								
								ts/mod_services/classes.serviceconfiguration.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,432 @@
 | 
				
			|||||||
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
 | 
					import * as helpers from './helpers.js';
 | 
				
			||||||
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					import { DockerContainer } from './classes.dockercontainer.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IServiceConfig {
 | 
				
			||||||
 | 
					  PROJECT_NAME: string;
 | 
				
			||||||
 | 
					  MONGODB_HOST: string;
 | 
				
			||||||
 | 
					  MONGODB_NAME: string;
 | 
				
			||||||
 | 
					  MONGODB_PORT: string;
 | 
				
			||||||
 | 
					  MONGODB_USER: string;
 | 
				
			||||||
 | 
					  MONGODB_PASS: string;
 | 
				
			||||||
 | 
					  MONGODB_URL: string;
 | 
				
			||||||
 | 
					  S3_HOST: string;
 | 
				
			||||||
 | 
					  S3_PORT: string;
 | 
				
			||||||
 | 
					  S3_CONSOLE_PORT: string;
 | 
				
			||||||
 | 
					  S3_ACCESSKEY: string;
 | 
				
			||||||
 | 
					  S3_SECRETKEY: string;
 | 
				
			||||||
 | 
					  S3_BUCKET: string;
 | 
				
			||||||
 | 
					  S3_ENDPOINT: string;
 | 
				
			||||||
 | 
					  S3_USESSL: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ServiceConfiguration {
 | 
				
			||||||
 | 
					  private configPath: string;
 | 
				
			||||||
 | 
					  private config: IServiceConfig;
 | 
				
			||||||
 | 
					  private docker: DockerContainer;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    this.configPath = plugins.path.join(process.cwd(), '.nogit', 'env.json');
 | 
				
			||||||
 | 
					    this.docker = new DockerContainer();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Load or create the configuration
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async loadOrCreate(): Promise<IServiceConfig> {
 | 
				
			||||||
 | 
					    await this.ensureNogitDirectory();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (await this.configExists()) {
 | 
				
			||||||
 | 
					      await this.loadConfig();
 | 
				
			||||||
 | 
					      await this.updateMissingFields();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      await this.createDefaultConfig();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Sync ports from existing Docker containers if they exist
 | 
				
			||||||
 | 
					    await this.syncPortsFromDocker();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return this.config;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get the current configuration
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public getConfig(): IServiceConfig {
 | 
				
			||||||
 | 
					    return this.config;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Save the configuration to file
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async saveConfig(): Promise<void> {
 | 
				
			||||||
 | 
					    await plugins.smartfile.memory.toFs(
 | 
				
			||||||
 | 
					      JSON.stringify(this.config, null, 2),
 | 
				
			||||||
 | 
					      this.configPath
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Ensure .nogit directory exists
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async ensureNogitDirectory(): Promise<void> {
 | 
				
			||||||
 | 
					    const nogitPath = plugins.path.join(process.cwd(), '.nogit');
 | 
				
			||||||
 | 
					    await plugins.smartfile.fs.ensureDir(nogitPath);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Check if configuration file exists
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async configExists(): Promise<boolean> {
 | 
				
			||||||
 | 
					    return plugins.smartfile.fs.fileExists(this.configPath);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Load configuration from file
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async loadConfig(): Promise<void> {
 | 
				
			||||||
 | 
					    const configContent = plugins.smartfile.fs.toStringSync(this.configPath);
 | 
				
			||||||
 | 
					    this.config = JSON.parse(configContent);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Create default configuration
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async createDefaultConfig(): Promise<void> {
 | 
				
			||||||
 | 
					    const projectName = helpers.getProjectName();
 | 
				
			||||||
 | 
					    const mongoPort = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					    const s3Port = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					    let s3ConsolePort = s3Port + 1;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Ensure console port is also available
 | 
				
			||||||
 | 
					    while (!(await helpers.isPortAvailable(s3ConsolePort))) {
 | 
				
			||||||
 | 
					      s3ConsolePort++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const mongoUser = 'defaultadmin';
 | 
				
			||||||
 | 
					    const mongoPass = 'defaultpass';
 | 
				
			||||||
 | 
					    const mongoHost = 'localhost';
 | 
				
			||||||
 | 
					    const mongoName = projectName;
 | 
				
			||||||
 | 
					    const mongoPortStr = mongoPort.toString();
 | 
				
			||||||
 | 
					    const s3Host = 'localhost';
 | 
				
			||||||
 | 
					    const s3PortStr = s3Port.toString();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    this.config = {
 | 
				
			||||||
 | 
					      PROJECT_NAME: projectName,
 | 
				
			||||||
 | 
					      MONGODB_HOST: mongoHost,
 | 
				
			||||||
 | 
					      MONGODB_NAME: mongoName,
 | 
				
			||||||
 | 
					      MONGODB_PORT: mongoPortStr,
 | 
				
			||||||
 | 
					      MONGODB_USER: mongoUser,
 | 
				
			||||||
 | 
					      MONGODB_PASS: mongoPass,
 | 
				
			||||||
 | 
					      MONGODB_URL: `mongodb://${mongoUser}:${mongoPass}@${mongoHost}:${mongoPortStr}/${mongoName}?authSource=admin`,
 | 
				
			||||||
 | 
					      S3_HOST: s3Host,
 | 
				
			||||||
 | 
					      S3_PORT: s3PortStr,
 | 
				
			||||||
 | 
					      S3_CONSOLE_PORT: s3ConsolePort.toString(),
 | 
				
			||||||
 | 
					      S3_ACCESSKEY: 'defaultadmin',
 | 
				
			||||||
 | 
					      S3_SECRETKEY: 'defaultpass',
 | 
				
			||||||
 | 
					      S3_BUCKET: `${projectName}-documents`,
 | 
				
			||||||
 | 
					      S3_ENDPOINT: s3Host,
 | 
				
			||||||
 | 
					      S3_USESSL: false
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    await this.saveConfig();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('ok', '✅ Created .nogit/env.json with project defaults');
 | 
				
			||||||
 | 
					    logger.log('info', `📍 MongoDB port: ${mongoPort}`);
 | 
				
			||||||
 | 
					    logger.log('info', `📍 S3 API port: ${s3Port}`);
 | 
				
			||||||
 | 
					    logger.log('info', `📍 S3 Console port: ${s3ConsolePort}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Update missing fields in existing configuration
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async updateMissingFields(): Promise<void> {
 | 
				
			||||||
 | 
					    const projectName = helpers.getProjectName();
 | 
				
			||||||
 | 
					    let updated = false;
 | 
				
			||||||
 | 
					    const fieldsAdded: string[] = [];
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Check and add missing fields
 | 
				
			||||||
 | 
					    if (!this.config.PROJECT_NAME) {
 | 
				
			||||||
 | 
					      this.config.PROJECT_NAME = projectName;
 | 
				
			||||||
 | 
					      fieldsAdded.push('PROJECT_NAME');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.MONGODB_HOST) {
 | 
				
			||||||
 | 
					      this.config.MONGODB_HOST = 'localhost';
 | 
				
			||||||
 | 
					      fieldsAdded.push('MONGODB_HOST');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.MONGODB_NAME) {
 | 
				
			||||||
 | 
					      this.config.MONGODB_NAME = projectName;
 | 
				
			||||||
 | 
					      fieldsAdded.push('MONGODB_NAME');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.MONGODB_PORT) {
 | 
				
			||||||
 | 
					      const port = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					      this.config.MONGODB_PORT = port.toString();
 | 
				
			||||||
 | 
					      fieldsAdded.push(`MONGODB_PORT(${port})`);
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.MONGODB_USER) {
 | 
				
			||||||
 | 
					      this.config.MONGODB_USER = 'defaultadmin';
 | 
				
			||||||
 | 
					      fieldsAdded.push('MONGODB_USER');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.MONGODB_PASS) {
 | 
				
			||||||
 | 
					      this.config.MONGODB_PASS = 'defaultpass';
 | 
				
			||||||
 | 
					      fieldsAdded.push('MONGODB_PASS');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Always update MONGODB_URL based on current settings
 | 
				
			||||||
 | 
					    const oldUrl = this.config.MONGODB_URL;
 | 
				
			||||||
 | 
					    this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					    if (oldUrl !== this.config.MONGODB_URL) {
 | 
				
			||||||
 | 
					      fieldsAdded.push('MONGODB_URL');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.S3_HOST) {
 | 
				
			||||||
 | 
					      this.config.S3_HOST = 'localhost';
 | 
				
			||||||
 | 
					      fieldsAdded.push('S3_HOST');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.S3_PORT) {
 | 
				
			||||||
 | 
					      const port = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					      this.config.S3_PORT = port.toString();
 | 
				
			||||||
 | 
					      fieldsAdded.push(`S3_PORT(${port})`);
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.S3_CONSOLE_PORT) {
 | 
				
			||||||
 | 
					      const s3Port = parseInt(this.config.S3_PORT);
 | 
				
			||||||
 | 
					      let consolePort = s3Port + 1;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      while (!(await helpers.isPortAvailable(consolePort))) {
 | 
				
			||||||
 | 
					        consolePort++;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      this.config.S3_CONSOLE_PORT = consolePort.toString();
 | 
				
			||||||
 | 
					      fieldsAdded.push(`S3_CONSOLE_PORT(${consolePort})`);
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.S3_ACCESSKEY) {
 | 
				
			||||||
 | 
					      this.config.S3_ACCESSKEY = 'defaultadmin';
 | 
				
			||||||
 | 
					      fieldsAdded.push('S3_ACCESSKEY');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.S3_SECRETKEY) {
 | 
				
			||||||
 | 
					      this.config.S3_SECRETKEY = 'defaultpass';
 | 
				
			||||||
 | 
					      fieldsAdded.push('S3_SECRETKEY');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.S3_BUCKET) {
 | 
				
			||||||
 | 
					      this.config.S3_BUCKET = `${projectName}-documents`;
 | 
				
			||||||
 | 
					      fieldsAdded.push('S3_BUCKET');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!this.config.S3_USESSL) {
 | 
				
			||||||
 | 
					      this.config.S3_USESSL = false;
 | 
				
			||||||
 | 
					      fieldsAdded.push('S3_USESSL');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Always update S3_ENDPOINT based on current settings
 | 
				
			||||||
 | 
					    const oldEndpoint = this.config.S3_ENDPOINT;
 | 
				
			||||||
 | 
					    this.config.S3_ENDPOINT = this.config.S3_HOST;
 | 
				
			||||||
 | 
					    if (oldEndpoint !== this.config.S3_ENDPOINT) {
 | 
				
			||||||
 | 
					      fieldsAdded.push('S3_ENDPOINT');
 | 
				
			||||||
 | 
					      updated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (updated) {
 | 
				
			||||||
 | 
					      await this.saveConfig();
 | 
				
			||||||
 | 
					      logger.log('ok', `✅ Added missing fields: ${fieldsAdded.join(', ')}`);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.log('ok', '✅ Configuration complete');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get MongoDB connection string
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public getMongoConnectionString(useNetworkIp: boolean = false): string {
 | 
				
			||||||
 | 
					    const host = useNetworkIp ? '${networkIp}' : this.config.MONGODB_HOST;
 | 
				
			||||||
 | 
					    return `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${host}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get container names
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public getContainerNames() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      mongo: `${this.config.PROJECT_NAME}-mongodb`,
 | 
				
			||||||
 | 
					      minio: `${this.config.PROJECT_NAME}-minio`
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get data directories
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public getDataDirectories() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      mongo: plugins.path.join(process.cwd(), '.nogit', 'mongodata'),
 | 
				
			||||||
 | 
					      minio: plugins.path.join(process.cwd(), '.nogit', 'miniodata')
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Sync port configuration from existing Docker containers
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private async syncPortsFromDocker(): Promise<void> {
 | 
				
			||||||
 | 
					    const containers = this.getContainerNames();
 | 
				
			||||||
 | 
					    let updated = false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Check MongoDB container
 | 
				
			||||||
 | 
					    const mongoStatus = await this.docker.getStatus(containers.mongo);
 | 
				
			||||||
 | 
					    if (mongoStatus !== 'not_exists') {
 | 
				
			||||||
 | 
					      const portMappings = await this.docker.getPortMappings(containers.mongo);
 | 
				
			||||||
 | 
					      if (portMappings && portMappings['27017']) {
 | 
				
			||||||
 | 
					        const dockerPort = portMappings['27017'];
 | 
				
			||||||
 | 
					        if (this.config.MONGODB_PORT !== dockerPort) {
 | 
				
			||||||
 | 
					          logger.log('note', `📍 Syncing MongoDB port from Docker: ${dockerPort}`);
 | 
				
			||||||
 | 
					          this.config.MONGODB_PORT = dockerPort;
 | 
				
			||||||
 | 
					          updated = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Check MinIO container
 | 
				
			||||||
 | 
					    const minioStatus = await this.docker.getStatus(containers.minio);
 | 
				
			||||||
 | 
					    if (minioStatus !== 'not_exists') {
 | 
				
			||||||
 | 
					      const portMappings = await this.docker.getPortMappings(containers.minio);
 | 
				
			||||||
 | 
					      if (portMappings) {
 | 
				
			||||||
 | 
					        if (portMappings['9000']) {
 | 
				
			||||||
 | 
					          const dockerPort = portMappings['9000'];
 | 
				
			||||||
 | 
					          if (this.config.S3_PORT !== dockerPort) {
 | 
				
			||||||
 | 
					            logger.log('note', `📍 Syncing S3 API port from Docker: ${dockerPort}`);
 | 
				
			||||||
 | 
					            this.config.S3_PORT = dockerPort;
 | 
				
			||||||
 | 
					            updated = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (portMappings['9001']) {
 | 
				
			||||||
 | 
					          const dockerPort = portMappings['9001'];
 | 
				
			||||||
 | 
					          if (this.config.S3_CONSOLE_PORT !== dockerPort) {
 | 
				
			||||||
 | 
					            logger.log('note', `📍 Syncing S3 Console port from Docker: ${dockerPort}`);
 | 
				
			||||||
 | 
					            this.config.S3_CONSOLE_PORT = dockerPort;
 | 
				
			||||||
 | 
					            updated = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (updated) {
 | 
				
			||||||
 | 
					      // Update derived fields
 | 
				
			||||||
 | 
					      this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					      this.config.S3_ENDPOINT = this.config.S3_HOST;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      await this.saveConfig();
 | 
				
			||||||
 | 
					      logger.log('ok', '✅ Configuration synced with Docker containers');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Validate and update ports if they're not available
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async validateAndUpdatePorts(): Promise<boolean> {
 | 
				
			||||||
 | 
					    let updated = false;
 | 
				
			||||||
 | 
					    const containers = this.getContainerNames();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Check if containers exist - if they do, ports are fine
 | 
				
			||||||
 | 
					    const mongoExists = await this.docker.exists(containers.mongo);
 | 
				
			||||||
 | 
					    const minioExists = await this.docker.exists(containers.minio);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Only check port availability if containers don't exist
 | 
				
			||||||
 | 
					    if (!mongoExists) {
 | 
				
			||||||
 | 
					      const mongoPort = parseInt(this.config.MONGODB_PORT);
 | 
				
			||||||
 | 
					      if (!(await helpers.isPortAvailable(mongoPort))) {
 | 
				
			||||||
 | 
					        logger.log('note', `⚠️  MongoDB port ${mongoPort} is in use, finding new port...`);
 | 
				
			||||||
 | 
					        const newPort = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					        this.config.MONGODB_PORT = newPort.toString();
 | 
				
			||||||
 | 
					        logger.log('ok', `✅ New MongoDB port: ${newPort}`);
 | 
				
			||||||
 | 
					        updated = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!minioExists) {
 | 
				
			||||||
 | 
					      const s3Port = parseInt(this.config.S3_PORT);
 | 
				
			||||||
 | 
					      const s3ConsolePort = parseInt(this.config.S3_CONSOLE_PORT);
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (!(await helpers.isPortAvailable(s3Port))) {
 | 
				
			||||||
 | 
					        logger.log('note', `⚠️  S3 API port ${s3Port} is in use, finding new port...`);
 | 
				
			||||||
 | 
					        const newPort = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					        this.config.S3_PORT = newPort.toString();
 | 
				
			||||||
 | 
					        logger.log('ok', `✅ New S3 API port: ${newPort}`);
 | 
				
			||||||
 | 
					        updated = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (!(await helpers.isPortAvailable(s3ConsolePort))) {
 | 
				
			||||||
 | 
					        logger.log('note', `⚠️  S3 Console port ${s3ConsolePort} is in use, finding new port...`);
 | 
				
			||||||
 | 
					        let newPort = parseInt(this.config.S3_PORT) + 1;
 | 
				
			||||||
 | 
					        while (!(await helpers.isPortAvailable(newPort))) {
 | 
				
			||||||
 | 
					          newPort++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.config.S3_CONSOLE_PORT = newPort.toString();
 | 
				
			||||||
 | 
					        logger.log('ok', `✅ New S3 Console port: ${newPort}`);
 | 
				
			||||||
 | 
					        updated = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (updated) {
 | 
				
			||||||
 | 
					      // Update derived fields
 | 
				
			||||||
 | 
					      this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					      this.config.S3_ENDPOINT = this.config.S3_HOST;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      await this.saveConfig();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return updated;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Force reconfigure all ports with new available ones
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async reconfigurePorts(): Promise<void> {
 | 
				
			||||||
 | 
					    logger.log('note', '🔄 Finding new available ports...');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const mongoPort = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					    const s3Port = await helpers.getRandomAvailablePort();
 | 
				
			||||||
 | 
					    let s3ConsolePort = s3Port + 1;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Ensure console port is also available
 | 
				
			||||||
 | 
					    while (!(await helpers.isPortAvailable(s3ConsolePort))) {
 | 
				
			||||||
 | 
					      s3ConsolePort++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    this.config.MONGODB_PORT = mongoPort.toString();
 | 
				
			||||||
 | 
					    this.config.S3_PORT = s3Port.toString();
 | 
				
			||||||
 | 
					    this.config.S3_CONSOLE_PORT = s3ConsolePort.toString();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Update derived fields
 | 
				
			||||||
 | 
					    this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					    this.config.S3_ENDPOINT = this.config.S3_HOST;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    await this.saveConfig();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('ok', '✅ New port configuration:');
 | 
				
			||||||
 | 
					    logger.log('info', `  📍 MongoDB: ${mongoPort}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  📍 S3 API: ${s3Port}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  📍 S3 Console: ${s3ConsolePort}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										583
									
								
								ts/mod_services/classes.servicemanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										583
									
								
								ts/mod_services/classes.servicemanager.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,583 @@
 | 
				
			|||||||
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
 | 
					import * as helpers from './helpers.js';
 | 
				
			||||||
 | 
					import { ServiceConfiguration } from './classes.serviceconfiguration.js';
 | 
				
			||||||
 | 
					import { DockerContainer } from './classes.dockercontainer.js';
 | 
				
			||||||
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ServiceManager {
 | 
				
			||||||
 | 
					  private config: ServiceConfiguration;
 | 
				
			||||||
 | 
					  private docker: DockerContainer;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    this.config = new ServiceConfiguration();
 | 
				
			||||||
 | 
					    this.docker = new DockerContainer();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Initialize the service manager
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async init(): Promise<void> {
 | 
				
			||||||
 | 
					    // Check Docker availability
 | 
				
			||||||
 | 
					    if (!(await this.docker.checkDocker())) {
 | 
				
			||||||
 | 
					      logger.log('error', 'Error: Docker is not installed. Please install Docker first.');
 | 
				
			||||||
 | 
					      process.exit(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Load or create configuration
 | 
				
			||||||
 | 
					    await this.config.loadOrCreate();
 | 
				
			||||||
 | 
					    logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Validate and update ports if needed
 | 
				
			||||||
 | 
					    await this.config.validateAndUpdatePorts();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Start MongoDB service
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async startMongoDB(): Promise<void> {
 | 
				
			||||||
 | 
					    logger.log('note', '📦 MongoDB:');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const config = this.config.getConfig();
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    const directories = this.config.getDataDirectories();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Ensure data directory exists
 | 
				
			||||||
 | 
					    await plugins.smartfile.fs.ensureDir(directories.mongo);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const status = await this.docker.getStatus(containers.mongo);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    switch (status) {
 | 
				
			||||||
 | 
					      case 'running':
 | 
				
			||||||
 | 
					        logger.log('ok', '  Already running ✓');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'stopped':
 | 
				
			||||||
 | 
					        // Check if port mapping matches config
 | 
				
			||||||
 | 
					        const mongoPortMappings = await this.docker.getPortMappings(containers.mongo);
 | 
				
			||||||
 | 
					        if (mongoPortMappings && mongoPortMappings['27017'] !== config.MONGODB_PORT) {
 | 
				
			||||||
 | 
					          logger.log('note', '  Port configuration changed, recreating container...');
 | 
				
			||||||
 | 
					          await this.docker.remove(containers.mongo, true);
 | 
				
			||||||
 | 
					          // Fall through to create new container
 | 
				
			||||||
 | 
					          const success = await this.docker.run({
 | 
				
			||||||
 | 
					            name: containers.mongo,
 | 
				
			||||||
 | 
					            image: 'mongo:7.0',
 | 
				
			||||||
 | 
					            ports: {
 | 
				
			||||||
 | 
					              [`0.0.0.0:${config.MONGODB_PORT}`]: '27017'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            volumes: {
 | 
				
			||||||
 | 
					              [directories.mongo]: '/data/db'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            environment: {
 | 
				
			||||||
 | 
					              MONGO_INITDB_ROOT_USERNAME: config.MONGODB_USER,
 | 
				
			||||||
 | 
					              MONGO_INITDB_ROOT_PASSWORD: config.MONGODB_PASS,
 | 
				
			||||||
 | 
					              MONGO_INITDB_DATABASE: config.MONGODB_NAME
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            restart: 'unless-stopped',
 | 
				
			||||||
 | 
					            command: '--bind_ip_all'
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          if (success) {
 | 
				
			||||||
 | 
					            logger.log('ok', '  Recreated with new port ✓');
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            logger.log('error', '  Failed to recreate container');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          // Ports match, just start the container
 | 
				
			||||||
 | 
					          if (await this.docker.start(containers.mongo)) {
 | 
				
			||||||
 | 
					            logger.log('ok', '  Started ✓');
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            logger.log('error', '  Failed to start');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'not_exists':
 | 
				
			||||||
 | 
					        logger.log('note', '  Creating container...');
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        const success = await this.docker.run({
 | 
				
			||||||
 | 
					          name: containers.mongo,
 | 
				
			||||||
 | 
					          image: 'mongo:7.0',
 | 
				
			||||||
 | 
					          ports: {
 | 
				
			||||||
 | 
					            [`0.0.0.0:${config.MONGODB_PORT}`]: '27017'
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          volumes: {
 | 
				
			||||||
 | 
					            [directories.mongo]: '/data/db'
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          environment: {
 | 
				
			||||||
 | 
					            MONGO_INITDB_ROOT_USERNAME: config.MONGODB_USER,
 | 
				
			||||||
 | 
					            MONGO_INITDB_ROOT_PASSWORD: config.MONGODB_PASS,
 | 
				
			||||||
 | 
					            MONGO_INITDB_DATABASE: config.MONGODB_NAME
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          restart: 'unless-stopped',
 | 
				
			||||||
 | 
					          command: '--bind_ip_all'
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (success) {
 | 
				
			||||||
 | 
					          logger.log('ok', '  Created and started ✓');
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          logger.log('error', '  Failed to create container');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('info', `  Container: ${containers.mongo}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Port: ${config.MONGODB_PORT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Connection: ${this.config.getMongoConnectionString()}`);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Show Compass connection string
 | 
				
			||||||
 | 
					    const networkIp = await helpers.getLocalNetworkIp();
 | 
				
			||||||
 | 
					    const compassString = `mongodb://${config.MONGODB_USER}:${config.MONGODB_PASS}@${networkIp}:${config.MONGODB_PORT}/${config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					    logger.log('ok', `  Compass: ${compassString}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Start MinIO service
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async startMinIO(): Promise<void> {
 | 
				
			||||||
 | 
					    logger.log('note', '📦 S3/MinIO:');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const config = this.config.getConfig();
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    const directories = this.config.getDataDirectories();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Ensure data directory exists
 | 
				
			||||||
 | 
					    await plugins.smartfile.fs.ensureDir(directories.minio);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const status = await this.docker.getStatus(containers.minio);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    switch (status) {
 | 
				
			||||||
 | 
					      case 'running':
 | 
				
			||||||
 | 
					        logger.log('ok', '  Already running ✓');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'stopped':
 | 
				
			||||||
 | 
					        // Check if port mapping matches config
 | 
				
			||||||
 | 
					        const minioPortMappings = await this.docker.getPortMappings(containers.minio);
 | 
				
			||||||
 | 
					        if (minioPortMappings && 
 | 
				
			||||||
 | 
					            (minioPortMappings['9000'] !== config.S3_PORT || 
 | 
				
			||||||
 | 
					             minioPortMappings['9001'] !== config.S3_CONSOLE_PORT)) {
 | 
				
			||||||
 | 
					          logger.log('note', '  Port configuration changed, recreating container...');
 | 
				
			||||||
 | 
					          await this.docker.remove(containers.minio, true);
 | 
				
			||||||
 | 
					          // Fall through to create new container
 | 
				
			||||||
 | 
					          const success = await this.docker.run({
 | 
				
			||||||
 | 
					            name: containers.minio,
 | 
				
			||||||
 | 
					            image: 'minio/minio',
 | 
				
			||||||
 | 
					            ports: {
 | 
				
			||||||
 | 
					              [config.S3_PORT]: '9000',
 | 
				
			||||||
 | 
					              [config.S3_CONSOLE_PORT]: '9001'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            volumes: {
 | 
				
			||||||
 | 
					              [directories.minio]: '/data'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            environment: {
 | 
				
			||||||
 | 
					              MINIO_ROOT_USER: config.S3_ACCESSKEY,
 | 
				
			||||||
 | 
					              MINIO_ROOT_PASSWORD: config.S3_SECRETKEY
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            restart: 'unless-stopped',
 | 
				
			||||||
 | 
					            command: 'server /data --console-address ":9001"'
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          if (success) {
 | 
				
			||||||
 | 
					            logger.log('ok', '  Recreated with new ports ✓');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Wait for MinIO to be ready
 | 
				
			||||||
 | 
					            await plugins.smartdelay.delayFor(3000);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Create default bucket
 | 
				
			||||||
 | 
					            await this.docker.exec(
 | 
				
			||||||
 | 
					              containers.minio,
 | 
				
			||||||
 | 
					              `mc alias set local http://localhost:9000 ${config.S3_ACCESSKEY} ${config.S3_SECRETKEY}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            await this.docker.exec(
 | 
				
			||||||
 | 
					              containers.minio,
 | 
				
			||||||
 | 
					              `mc mb local/${config.S3_BUCKET}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            logger.log('ok', `  Bucket '${config.S3_BUCKET}' created ✓`);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            logger.log('error', '  Failed to recreate container');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          // Ports match, just start the container
 | 
				
			||||||
 | 
					          if (await this.docker.start(containers.minio)) {
 | 
				
			||||||
 | 
					            logger.log('ok', '  Started ✓');
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            logger.log('error', '  Failed to start');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'not_exists':
 | 
				
			||||||
 | 
					        logger.log('note', '  Creating container...');
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        const success = await this.docker.run({
 | 
				
			||||||
 | 
					          name: containers.minio,
 | 
				
			||||||
 | 
					          image: 'minio/minio',
 | 
				
			||||||
 | 
					          ports: {
 | 
				
			||||||
 | 
					            [config.S3_PORT]: '9000',
 | 
				
			||||||
 | 
					            [config.S3_CONSOLE_PORT]: '9001'
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          volumes: {
 | 
				
			||||||
 | 
					            [directories.minio]: '/data'
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          environment: {
 | 
				
			||||||
 | 
					            MINIO_ROOT_USER: config.S3_ACCESSKEY,
 | 
				
			||||||
 | 
					            MINIO_ROOT_PASSWORD: config.S3_SECRETKEY
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          restart: 'unless-stopped',
 | 
				
			||||||
 | 
					          command: 'server /data --console-address ":9001"'
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (success) {
 | 
				
			||||||
 | 
					          logger.log('ok', '  Created and started ✓');
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          // Wait for MinIO to be ready
 | 
				
			||||||
 | 
					          await plugins.smartdelay.delayFor(3000);
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          // Create default bucket
 | 
				
			||||||
 | 
					          await this.docker.exec(
 | 
				
			||||||
 | 
					            containers.minio,
 | 
				
			||||||
 | 
					            `mc alias set local http://localhost:9000 ${config.S3_ACCESSKEY} ${config.S3_SECRETKEY}`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          await this.docker.exec(
 | 
				
			||||||
 | 
					            containers.minio,
 | 
				
			||||||
 | 
					            `mc mb local/${config.S3_BUCKET}`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          logger.log('ok', `  Bucket '${config.S3_BUCKET}' created ✓`);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          logger.log('error', '  Failed to create container');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('info', `  Container: ${containers.minio}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Port: ${config.S3_PORT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Bucket: ${config.S3_BUCKET}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  API: http://${config.S3_HOST}:${config.S3_PORT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT} (login: ${config.S3_ACCESSKEY}/***)`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Stop MongoDB service
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async stopMongoDB(): Promise<void> {
 | 
				
			||||||
 | 
					    logger.log('note', '📦 MongoDB:');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    const status = await this.docker.getStatus(containers.mongo);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (status === 'running') {
 | 
				
			||||||
 | 
					      if (await this.docker.stop(containers.mongo)) {
 | 
				
			||||||
 | 
					        logger.log('ok', '  Stopped ✓');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        logger.log('error', '  Failed to stop');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.log('note', '  Not running');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Stop MinIO service
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async stopMinIO(): Promise<void> {
 | 
				
			||||||
 | 
					    logger.log('note', '📦 S3/MinIO:');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    const status = await this.docker.getStatus(containers.minio);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (status === 'running') {
 | 
				
			||||||
 | 
					      if (await this.docker.stop(containers.minio)) {
 | 
				
			||||||
 | 
					        logger.log('ok', '  Stopped ✓');
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        logger.log('error', '  Failed to stop');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      logger.log('note', '  Not running');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Show service status
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async showStatus(): Promise<void> {
 | 
				
			||||||
 | 
					    helpers.printHeader('Service Status');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const config = this.config.getConfig();
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('info', `Project: ${config.PROJECT_NAME}`);
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // MongoDB status
 | 
				
			||||||
 | 
					    const mongoStatus = await this.docker.getStatus(containers.mongo);
 | 
				
			||||||
 | 
					    switch (mongoStatus) {
 | 
				
			||||||
 | 
					      case 'running':
 | 
				
			||||||
 | 
					        logger.log('ok', '📦 MongoDB: 🟢 Running');
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ Container: ${containers.mongo}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ Port: ${config.MONGODB_PORT}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ Connection: ${this.config.getMongoConnectionString()}`);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Show Compass connection string
 | 
				
			||||||
 | 
					        const networkIp = await helpers.getLocalNetworkIp();
 | 
				
			||||||
 | 
					        const compassString = `mongodb://${config.MONGODB_USER}:${config.MONGODB_PASS}@${networkIp}:${config.MONGODB_PORT}/${config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					        logger.log('ok', `   └─ Compass: ${compassString}`);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 'stopped':
 | 
				
			||||||
 | 
					        logger.log('note', '📦 MongoDB: 🟡 Stopped');
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ Container: ${containers.mongo}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   └─ Port: ${config.MONGODB_PORT}`);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 'not_exists':
 | 
				
			||||||
 | 
					        logger.log('info', '📦 MongoDB: ⚪ Not installed');
 | 
				
			||||||
 | 
					        // Check port availability
 | 
				
			||||||
 | 
					        const mongoPort = parseInt(config.MONGODB_PORT);
 | 
				
			||||||
 | 
					        const mongoAvailable = await helpers.isPortAvailable(mongoPort);
 | 
				
			||||||
 | 
					        if (!mongoAvailable) {
 | 
				
			||||||
 | 
					          logger.log('error', `   └─ ⚠️  Port ${mongoPort} is in use by another process`);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          logger.log('info', `   └─ Port ${mongoPort} is available`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // MinIO status
 | 
				
			||||||
 | 
					    const minioStatus = await this.docker.getStatus(containers.minio);
 | 
				
			||||||
 | 
					    switch (minioStatus) {
 | 
				
			||||||
 | 
					      case 'running':
 | 
				
			||||||
 | 
					        logger.log('ok', '📦 S3/MinIO: 🟢 Running');
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ Container: ${containers.minio}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ API: http://${config.S3_HOST}:${config.S3_PORT}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   └─ Bucket: ${config.S3_BUCKET}`);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 'stopped':
 | 
				
			||||||
 | 
					        logger.log('note', '📦 S3/MinIO: 🟡 Stopped');
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ Container: ${containers.minio}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   ├─ API Port: ${config.S3_PORT}`);
 | 
				
			||||||
 | 
					        logger.log('info', `   └─ Console Port: ${config.S3_CONSOLE_PORT}`);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 'not_exists':
 | 
				
			||||||
 | 
					        logger.log('info', '📦 S3/MinIO: ⚪ Not installed');
 | 
				
			||||||
 | 
					        // Check port availability
 | 
				
			||||||
 | 
					        const s3Port = parseInt(config.S3_PORT);
 | 
				
			||||||
 | 
					        const s3ConsolePort = parseInt(config.S3_CONSOLE_PORT);
 | 
				
			||||||
 | 
					        const s3Available = await helpers.isPortAvailable(s3Port);
 | 
				
			||||||
 | 
					        const consoleAvailable = await helpers.isPortAvailable(s3ConsolePort);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (!s3Available || !consoleAvailable) {
 | 
				
			||||||
 | 
					          if (!s3Available) {
 | 
				
			||||||
 | 
					            logger.log('error', `   ├─ ⚠️  API Port ${s3Port} is in use`);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            logger.log('info', `   ├─ API Port ${s3Port} is available`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (!consoleAvailable) {
 | 
				
			||||||
 | 
					            logger.log('error', `   └─ ⚠️  Console Port ${s3ConsolePort} is in use`);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            logger.log('info', `   └─ Console Port ${s3ConsolePort} is available`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          logger.log('info', `   ├─ API Port ${s3Port} is available`);
 | 
				
			||||||
 | 
					          logger.log('info', `   └─ Console Port ${s3ConsolePort} is available`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Show configuration
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async showConfig(): Promise<void> {
 | 
				
			||||||
 | 
					    helpers.printHeader('Current Configuration');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const config = this.config.getConfig();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('info', `Project: ${config.PROJECT_NAME}`);
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('note', 'MongoDB:');
 | 
				
			||||||
 | 
					    logger.log('info', `  Host: ${config.MONGODB_HOST}:${config.MONGODB_PORT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Database: ${config.MONGODB_NAME}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  User: ${config.MONGODB_USER}`);
 | 
				
			||||||
 | 
					    logger.log('info', '  Password: ***');
 | 
				
			||||||
 | 
					    logger.log('info', `  Container: ${this.config.getContainerNames().mongo}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Data: ${this.config.getDataDirectories().mongo}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Connection: ${this.config.getMongoConnectionString()}`);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    logger.log('note', 'S3/MinIO:');
 | 
				
			||||||
 | 
					    logger.log('info', `  Host: ${config.S3_HOST}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  API Port: ${config.S3_PORT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Console Port: ${config.S3_CONSOLE_PORT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Access Key: ${config.S3_ACCESSKEY}`);
 | 
				
			||||||
 | 
					    logger.log('info', '  Secret Key: ***');
 | 
				
			||||||
 | 
					    logger.log('info', `  Bucket: ${config.S3_BUCKET}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Use SSL: ${config.S3_USESSL}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Container: ${this.config.getContainerNames().minio}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Data: ${this.config.getDataDirectories().minio}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Endpoint: ${config.S3_ENDPOINT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Console URL: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Show MongoDB Compass connection string
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async showCompassConnection(): Promise<void> {
 | 
				
			||||||
 | 
					    helpers.printHeader('MongoDB Compass Connection');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const config = this.config.getConfig();
 | 
				
			||||||
 | 
					    const networkIp = await helpers.getLocalNetworkIp();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const connectionString = `mongodb://${config.MONGODB_USER}:${config.MONGODB_PASS}@${networkIp}:${config.MONGODB_PORT}/${config.MONGODB_NAME}?authSource=admin`;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    logger.log('info', 'MongoDB Compass is a GUI tool for MongoDB. To connect:');
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    logger.log('info', '1. Download MongoDB Compass from:');
 | 
				
			||||||
 | 
					    logger.log('info', '   https://www.mongodb.com/products/compass');
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    logger.log('info', '2. Open Compass and paste this connection string:');
 | 
				
			||||||
 | 
					    logger.log('ok', `   ${connectionString}`);
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    logger.log('note', 'Connection Details:');
 | 
				
			||||||
 | 
					    logger.log('info', `  Network IP: ${networkIp}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Port: ${config.MONGODB_PORT}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Database: ${config.MONGODB_NAME}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Username: ${config.MONGODB_USER}`);
 | 
				
			||||||
 | 
					    logger.log('info', `  Auth Source: admin`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Show logs for a service
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async showLogs(service: string, lines: number = 20): Promise<void> {
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    switch (service) {
 | 
				
			||||||
 | 
					      case 'mongo':
 | 
				
			||||||
 | 
					      case 'mongodb':
 | 
				
			||||||
 | 
					        if (await this.docker.isRunning(containers.mongo)) {
 | 
				
			||||||
 | 
					          helpers.printHeader(`MongoDB Logs (last ${lines} lines)`);
 | 
				
			||||||
 | 
					          const logs = await this.docker.logs(containers.mongo, lines);
 | 
				
			||||||
 | 
					          console.log(logs);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          logger.log('note', 'MongoDB container is not running');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'minio':
 | 
				
			||||||
 | 
					      case 's3':
 | 
				
			||||||
 | 
					        if (await this.docker.isRunning(containers.minio)) {
 | 
				
			||||||
 | 
					          helpers.printHeader(`S3/MinIO Logs (last ${lines} lines)`);
 | 
				
			||||||
 | 
					          const logs = await this.docker.logs(containers.minio, lines);
 | 
				
			||||||
 | 
					          console.log(logs);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          logger.log('note', 'S3/MinIO container is not running');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      case 'all':
 | 
				
			||||||
 | 
					      case '':
 | 
				
			||||||
 | 
					        await this.showLogs('mongo', lines);
 | 
				
			||||||
 | 
					        console.log();
 | 
				
			||||||
 | 
					        await this.showLogs('minio', lines);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        logger.log('note', 'Usage: gitzone services logs [mongo|s3|all] [lines]');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Remove containers
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async removeContainers(): Promise<void> {
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    let removed = false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (await this.docker.exists(containers.mongo)) {
 | 
				
			||||||
 | 
					      if (await this.docker.remove(containers.mongo, true)) {
 | 
				
			||||||
 | 
					        logger.log('ok', '  MongoDB container removed ✓');
 | 
				
			||||||
 | 
					        removed = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (await this.docker.exists(containers.minio)) {
 | 
				
			||||||
 | 
					      if (await this.docker.remove(containers.minio, true)) {
 | 
				
			||||||
 | 
					        logger.log('ok', '  S3/MinIO container removed ✓');
 | 
				
			||||||
 | 
					        removed = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!removed) {
 | 
				
			||||||
 | 
					      logger.log('note', '  No containers to remove');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Clean data directories
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async cleanData(): Promise<void> {
 | 
				
			||||||
 | 
					    const directories = this.config.getDataDirectories();
 | 
				
			||||||
 | 
					    let cleaned = false;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (await plugins.smartfile.fs.fileExists(directories.mongo)) {
 | 
				
			||||||
 | 
					      await plugins.smartfile.fs.remove(directories.mongo);
 | 
				
			||||||
 | 
					      logger.log('ok', '  MongoDB data removed ✓');
 | 
				
			||||||
 | 
					      cleaned = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (await plugins.smartfile.fs.fileExists(directories.minio)) {
 | 
				
			||||||
 | 
					      await plugins.smartfile.fs.remove(directories.minio);
 | 
				
			||||||
 | 
					      logger.log('ok', '  S3/MinIO data removed ✓');
 | 
				
			||||||
 | 
					      cleaned = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!cleaned) {
 | 
				
			||||||
 | 
					      logger.log('note', '  No data to clean');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Reconfigure services with new ports
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async reconfigure(): Promise<void> {
 | 
				
			||||||
 | 
					    helpers.printHeader('Reconfiguring Services');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const containers = this.config.getContainerNames();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Stop existing containers
 | 
				
			||||||
 | 
					    logger.log('note', '🛑 Stopping existing containers...');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (await this.docker.exists(containers.mongo)) {
 | 
				
			||||||
 | 
					      await this.docker.stop(containers.mongo);
 | 
				
			||||||
 | 
					      logger.log('ok', '  MongoDB stopped ✓');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (await this.docker.exists(containers.minio)) {
 | 
				
			||||||
 | 
					      await this.docker.stop(containers.minio);
 | 
				
			||||||
 | 
					      logger.log('ok', '  S3/MinIO stopped ✓');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Reconfigure ports
 | 
				
			||||||
 | 
					    await this.config.reconfigurePorts();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Ask if user wants to restart services
 | 
				
			||||||
 | 
					    const smartinteract = new plugins.smartinteract.SmartInteract();
 | 
				
			||||||
 | 
					    const response = await smartinteract.askQuestion({
 | 
				
			||||||
 | 
					      name: 'restart',
 | 
				
			||||||
 | 
					      type: 'confirm',
 | 
				
			||||||
 | 
					      message: 'Do you want to start services with new ports?',
 | 
				
			||||||
 | 
					      default: true
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (response.value) {
 | 
				
			||||||
 | 
					      console.log();
 | 
				
			||||||
 | 
					      await this.startMongoDB();
 | 
				
			||||||
 | 
					      await this.startMinIO();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										123
									
								
								ts/mod_services/helpers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								ts/mod_services/helpers.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
 | 
					import * as net from 'net';
 | 
				
			||||||
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Check if a port is available
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const isPortAvailable = async (port: number): Promise<boolean> => {
 | 
				
			||||||
 | 
					  return new Promise((resolve) => {
 | 
				
			||||||
 | 
					    const server = net.createServer();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    server.once('error', () => {
 | 
				
			||||||
 | 
					      resolve(false);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    server.once('listening', () => {
 | 
				
			||||||
 | 
					      server.close();
 | 
				
			||||||
 | 
					      resolve(true);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    server.listen(port, '0.0.0.0');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get a random available port between 20000 and 30000
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const getRandomAvailablePort = async (): Promise<number> => {
 | 
				
			||||||
 | 
					  const maxAttempts = 100;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  for (let i = 0; i < maxAttempts; i++) {
 | 
				
			||||||
 | 
					    const port = Math.floor(Math.random() * 10001) + 20000;
 | 
				
			||||||
 | 
					    if (await isPortAvailable(port)) {
 | 
				
			||||||
 | 
					      return port;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Fallback: let the system assign a port
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get the project name from package.json or directory
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const getProjectName = (): string => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const packageJsonPath = plugins.path.join(process.cwd(), 'package.json');
 | 
				
			||||||
 | 
					    if (plugins.smartfile.fs.fileExistsSync(packageJsonPath)) {
 | 
				
			||||||
 | 
					      const packageJson = plugins.smartfile.fs.toObjectSync(packageJsonPath);
 | 
				
			||||||
 | 
					      if (packageJson.name) {
 | 
				
			||||||
 | 
					        // Sanitize: @fin.cx/skr → fin-cx-skr
 | 
				
			||||||
 | 
					        return packageJson.name.replace(/@/g, '').replace(/[\/\.]/g, '-');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    // Ignore errors and fall back to directory name
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return plugins.path.basename(process.cwd());
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Print a header with decorative lines
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const printHeader = (title: string) => {
 | 
				
			||||||
 | 
					  console.log();
 | 
				
			||||||
 | 
					  logger.log('info', '═══════════════════════════════════════════════════════════════');
 | 
				
			||||||
 | 
					  logger.log('info', `  ${title}`);
 | 
				
			||||||
 | 
					  logger.log('info', '═══════════════════════════════════════════════════════════════');
 | 
				
			||||||
 | 
					  console.log();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Format bytes to human readable string
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const formatBytes = (bytes: number): string => {
 | 
				
			||||||
 | 
					  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
 | 
				
			||||||
 | 
					  let size = bytes;
 | 
				
			||||||
 | 
					  let unitIndex = 0;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  while (size >= 1024 && unitIndex < units.length - 1) {
 | 
				
			||||||
 | 
					    size /= 1024;
 | 
				
			||||||
 | 
					    unitIndex++;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  return `${size.toFixed(2)} ${units[unitIndex]}`;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get the local network IP address
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const getLocalNetworkIp = async (): Promise<string> => {
 | 
				
			||||||
 | 
					  const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork();
 | 
				
			||||||
 | 
					  const gateways = await smartnetworkInstance.getGateways();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Find the best local IP from network interfaces
 | 
				
			||||||
 | 
					  for (const interfaceName of Object.keys(gateways)) {
 | 
				
			||||||
 | 
					    const interfaces = gateways[interfaceName];
 | 
				
			||||||
 | 
					    for (const iface of interfaces) {
 | 
				
			||||||
 | 
					      // Skip loopback and internal interfaces
 | 
				
			||||||
 | 
					      if (!iface.internal && iface.family === 'IPv4') {
 | 
				
			||||||
 | 
					        const address = iface.address;
 | 
				
			||||||
 | 
					        // Prefer LAN IPs
 | 
				
			||||||
 | 
					        if (address.startsWith('192.168.') || address.startsWith('10.') || address.startsWith('172.')) {
 | 
				
			||||||
 | 
					          return address;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Fallback: try to get any non-internal IPv4
 | 
				
			||||||
 | 
					  for (const interfaceName of Object.keys(gateways)) {
 | 
				
			||||||
 | 
					    const interfaces = gateways[interfaceName];
 | 
				
			||||||
 | 
					    for (const iface of interfaces) {
 | 
				
			||||||
 | 
					      if (!iface.internal && iface.family === 'IPv4') {
 | 
				
			||||||
 | 
					        return iface.address;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Last resort: localhost
 | 
				
			||||||
 | 
					  return 'localhost';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										224
									
								
								ts/mod_services/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								ts/mod_services/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,224 @@
 | 
				
			|||||||
 | 
					import * as plugins from './mod.plugins.js';
 | 
				
			||||||
 | 
					import * as helpers from './helpers.js';
 | 
				
			||||||
 | 
					import { ServiceManager } from './classes.servicemanager.js';
 | 
				
			||||||
 | 
					import { logger } from '../gitzone.logging.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const run = async (argvArg: any) => {
 | 
				
			||||||
 | 
					  const serviceManager = new ServiceManager();
 | 
				
			||||||
 | 
					  await serviceManager.init();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const command = argvArg._[1] || 'help';
 | 
				
			||||||
 | 
					  const service = argvArg._[2] || 'all';
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  switch (command) {
 | 
				
			||||||
 | 
					    case 'start':
 | 
				
			||||||
 | 
					      await handleStart(serviceManager, service);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'stop':
 | 
				
			||||||
 | 
					      await handleStop(serviceManager, service);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'restart':
 | 
				
			||||||
 | 
					      await handleRestart(serviceManager, service);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'status':
 | 
				
			||||||
 | 
					      await serviceManager.showStatus();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'config':
 | 
				
			||||||
 | 
					      await serviceManager.showConfig();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'compass':
 | 
				
			||||||
 | 
					      await serviceManager.showCompassConnection();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'logs':
 | 
				
			||||||
 | 
					      const lines = parseInt(argvArg._[3]) || 20;
 | 
				
			||||||
 | 
					      await serviceManager.showLogs(service, lines);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'remove':
 | 
				
			||||||
 | 
					      await handleRemove(serviceManager);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'clean':
 | 
				
			||||||
 | 
					      await handleClean(serviceManager);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'reconfigure':
 | 
				
			||||||
 | 
					      await serviceManager.reconfigure();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'help':
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      showHelp();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function handleStart(serviceManager: ServiceManager, service: string) {
 | 
				
			||||||
 | 
					  helpers.printHeader('Starting Services');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  switch (service) {
 | 
				
			||||||
 | 
					    case 'mongo':
 | 
				
			||||||
 | 
					    case 'mongodb':
 | 
				
			||||||
 | 
					      await serviceManager.startMongoDB();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'minio':
 | 
				
			||||||
 | 
					    case 's3':
 | 
				
			||||||
 | 
					      await serviceManager.startMinIO();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'all':
 | 
				
			||||||
 | 
					    case '':
 | 
				
			||||||
 | 
					      await serviceManager.startMongoDB();
 | 
				
			||||||
 | 
					      console.log();
 | 
				
			||||||
 | 
					      await serviceManager.startMinIO();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      logger.log('error', `Unknown service: ${service}`);
 | 
				
			||||||
 | 
					      logger.log('note', 'Use: mongo, s3, or all');
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function handleStop(serviceManager: ServiceManager, service: string) {
 | 
				
			||||||
 | 
					  helpers.printHeader('Stopping Services');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  switch (service) {
 | 
				
			||||||
 | 
					    case 'mongo':
 | 
				
			||||||
 | 
					    case 'mongodb':
 | 
				
			||||||
 | 
					      await serviceManager.stopMongoDB();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'minio':
 | 
				
			||||||
 | 
					    case 's3':
 | 
				
			||||||
 | 
					      await serviceManager.stopMinIO();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'all':
 | 
				
			||||||
 | 
					    case '':
 | 
				
			||||||
 | 
					      await serviceManager.stopMongoDB();
 | 
				
			||||||
 | 
					      console.log();
 | 
				
			||||||
 | 
					      await serviceManager.stopMinIO();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      logger.log('error', `Unknown service: ${service}`);
 | 
				
			||||||
 | 
					      logger.log('note', 'Use: mongo, s3, or all');
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function handleRestart(serviceManager: ServiceManager, service: string) {
 | 
				
			||||||
 | 
					  helpers.printHeader('Restarting Services');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  switch (service) {
 | 
				
			||||||
 | 
					    case 'mongo':
 | 
				
			||||||
 | 
					    case 'mongodb':
 | 
				
			||||||
 | 
					      await serviceManager.stopMongoDB();
 | 
				
			||||||
 | 
					      await plugins.smartdelay.delayFor(2000);
 | 
				
			||||||
 | 
					      await serviceManager.startMongoDB();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'minio':
 | 
				
			||||||
 | 
					    case 's3':
 | 
				
			||||||
 | 
					      await serviceManager.stopMinIO();
 | 
				
			||||||
 | 
					      await plugins.smartdelay.delayFor(2000);
 | 
				
			||||||
 | 
					      await serviceManager.startMinIO();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    case 'all':
 | 
				
			||||||
 | 
					    case '':
 | 
				
			||||||
 | 
					      await serviceManager.stopMongoDB();
 | 
				
			||||||
 | 
					      await serviceManager.stopMinIO();
 | 
				
			||||||
 | 
					      await plugins.smartdelay.delayFor(2000);
 | 
				
			||||||
 | 
					      await serviceManager.startMongoDB();
 | 
				
			||||||
 | 
					      console.log();
 | 
				
			||||||
 | 
					      await serviceManager.startMinIO();
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      logger.log('error', `Unknown service: ${service}`);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function handleRemove(serviceManager: ServiceManager) {
 | 
				
			||||||
 | 
					  helpers.printHeader('Removing Containers');
 | 
				
			||||||
 | 
					  logger.log('note', '⚠️  This will remove containers but preserve data');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const shouldContinue = await plugins.smartinteract.SmartInteract.getCliConfirmation('Continue?', false);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (shouldContinue) {
 | 
				
			||||||
 | 
					    await serviceManager.removeContainers();
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    logger.log('note', 'Cancelled');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function handleClean(serviceManager: ServiceManager) {
 | 
				
			||||||
 | 
					  helpers.printHeader('Clean All');
 | 
				
			||||||
 | 
					  logger.log('error', '⚠️  WARNING: This will remove all containers and data!');
 | 
				
			||||||
 | 
					  logger.log('error', 'This action cannot be undone!');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const smartinteraction = new plugins.smartinteract.SmartInteract();
 | 
				
			||||||
 | 
					  const confirmAnswer = await smartinteraction.askQuestion({
 | 
				
			||||||
 | 
					    name: 'confirm',
 | 
				
			||||||
 | 
					    type: 'input',
 | 
				
			||||||
 | 
					    message: 'Type "yes" to confirm:',
 | 
				
			||||||
 | 
					    default: 'no'
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (confirmAnswer.value === 'yes') {
 | 
				
			||||||
 | 
					    await serviceManager.removeContainers();
 | 
				
			||||||
 | 
					    console.log();
 | 
				
			||||||
 | 
					    await serviceManager.cleanData();
 | 
				
			||||||
 | 
					    logger.log('ok', 'All cleaned ✓');
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    logger.log('note', 'Cancelled');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showHelp() {
 | 
				
			||||||
 | 
					  helpers.printHeader('GitZone Services Manager');
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  logger.log('ok', 'Usage: gitzone services [command] [options]');
 | 
				
			||||||
 | 
					  console.log();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  logger.log('note', 'Commands:');
 | 
				
			||||||
 | 
					  logger.log('info', '  start [service]   Start services (mongo|s3|all)');
 | 
				
			||||||
 | 
					  logger.log('info', '  stop [service]    Stop services (mongo|s3|all)');
 | 
				
			||||||
 | 
					  logger.log('info', '  restart [service] Restart services (mongo|s3|all)');
 | 
				
			||||||
 | 
					  logger.log('info', '  status            Show service status');
 | 
				
			||||||
 | 
					  logger.log('info', '  config            Show current configuration');
 | 
				
			||||||
 | 
					  logger.log('info', '  compass           Show MongoDB Compass connection string');
 | 
				
			||||||
 | 
					  logger.log('info', '  logs [service]    Show logs (mongo|s3|all) [lines]');
 | 
				
			||||||
 | 
					  logger.log('info', '  reconfigure       Reassign ports and restart services');
 | 
				
			||||||
 | 
					  logger.log('info', '  remove            Remove all containers');
 | 
				
			||||||
 | 
					  logger.log('info', '  clean             Remove all containers and data ⚠️');
 | 
				
			||||||
 | 
					  logger.log('info', '  help              Show this help message');
 | 
				
			||||||
 | 
					  console.log();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  logger.log('note', 'Features:');
 | 
				
			||||||
 | 
					  logger.log('info', '  • Auto-creates .nogit/env.json with smart defaults');
 | 
				
			||||||
 | 
					  logger.log('info', '  • Random ports (20000-30000) to avoid conflicts');
 | 
				
			||||||
 | 
					  logger.log('info', '  • Project-specific containers for multi-project support');
 | 
				
			||||||
 | 
					  logger.log('info', '  • Preserves custom configuration values');
 | 
				
			||||||
 | 
					  logger.log('info', '  • MongoDB Compass connection support');
 | 
				
			||||||
 | 
					  console.log();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  logger.log('note', 'Examples:');
 | 
				
			||||||
 | 
					  logger.log('info', '  gitzone services start          # Start all services');
 | 
				
			||||||
 | 
					  logger.log('info', '  gitzone services start mongo    # Start only MongoDB');
 | 
				
			||||||
 | 
					  logger.log('info', '  gitzone services stop           # Stop all services');
 | 
				
			||||||
 | 
					  logger.log('info', '  gitzone services status         # Check service status');
 | 
				
			||||||
 | 
					  logger.log('info', '  gitzone services config         # Show configuration');
 | 
				
			||||||
 | 
					  logger.log('info', '  gitzone services compass        # Get MongoDB Compass connection');
 | 
				
			||||||
 | 
					  logger.log('info', '  gitzone services logs mongo 50  # Show last 50 lines of MongoDB logs');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								ts/mod_services/mod.plugins.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								ts/mod_services/mod.plugins.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					export * from '../plugins.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as smartshell from '@push.rocks/smartshell';
 | 
				
			||||||
 | 
					import * as smartfile from '@push.rocks/smartfile';
 | 
				
			||||||
 | 
					import * as smartinteract from '@push.rocks/smartinteract';
 | 
				
			||||||
 | 
					import * as smartnetwork from '@push.rocks/smartnetwork';
 | 
				
			||||||
 | 
					import * as smartdelay from '@push.rocks/smartdelay';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { smartshell, smartfile, smartinteract, smartnetwork, smartdelay };
 | 
				
			||||||
@@ -16,7 +16,9 @@ export let run = () => {
 | 
				
			|||||||
    * create a new project with 'gitzone template [template]'
 | 
					    * create a new project with 'gitzone template [template]'
 | 
				
			||||||
      the following templates exist: ${(() => {
 | 
					      the following templates exist: ${(() => {
 | 
				
			||||||
        let projects = `\n`;
 | 
					        let projects = `\n`;
 | 
				
			||||||
        for (const template of plugins.smartfile.fs.listFoldersSync(paths.templatesDir)) {
 | 
					        for (const template of plugins.smartfile.fs.listFoldersSync(
 | 
				
			||||||
 | 
					          paths.templatesDir,
 | 
				
			||||||
 | 
					        )) {
 | 
				
			||||||
          projects += `       - ${template}\n`;
 | 
					          projects += `       - ${template}\n`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return projects;
 | 
					        return projects;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,9 @@ export const run = async (argvArg: any) => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await smartshellInstance.execStrict(`cd ${paths.cwd} && git checkout master`);
 | 
					  await smartshellInstance.execStrict(`cd ${paths.cwd} && git checkout master`);
 | 
				
			||||||
  await smartshellInstance.execStrict(`cd ${paths.cwd} && git pull origin master`);
 | 
					  await smartshellInstance.execStrict(
 | 
				
			||||||
 | 
					    `cd ${paths.cwd} && git pull origin master`,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
  await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`);
 | 
					  await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  await provideNoGitFiles();
 | 
					  await provideNoGitFiles();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,9 @@ export const isTemplate = async (templateNameArg: string) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const getTemplate = async (templateNameArg: string) => {
 | 
					export const getTemplate = async (templateNameArg: string) => {
 | 
				
			||||||
  if (isTemplate(templateNameArg)) {
 | 
					  if (isTemplate(templateNameArg)) {
 | 
				
			||||||
    const localScafTemplate = new plugins.smartscaf.ScafTemplate(getTemplatePath(templateNameArg));
 | 
					    const localScafTemplate = new plugins.smartscaf.ScafTemplate(
 | 
				
			||||||
 | 
					      getTemplatePath(templateNameArg),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    await localScafTemplate.readTemplateFromDir();
 | 
					    await localScafTemplate.readTemplateFromDir();
 | 
				
			||||||
    return localScafTemplate;
 | 
					    return localScafTemplate;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
@@ -32,7 +34,8 @@ export const run = async (argvArg: any) => {
 | 
				
			|||||||
    const answerBucket = await smartinteract.askQuestion({
 | 
					    const answerBucket = await smartinteract.askQuestion({
 | 
				
			||||||
      type: 'list',
 | 
					      type: 'list',
 | 
				
			||||||
      default: 'npm',
 | 
					      default: 'npm',
 | 
				
			||||||
      message: 'What template do you want to scaffold? (Only showing mpost common options)',
 | 
					      message:
 | 
				
			||||||
 | 
					        'What template do you want to scaffold? (Only showing mpost common options)',
 | 
				
			||||||
      name: 'templateName',
 | 
					      name: 'templateName',
 | 
				
			||||||
      choices: ['npm', 'service', 'wcc', 'website'],
 | 
					      choices: ['npm', 'service', 'wcc', 'website'],
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,11 @@ import * as smartcli from '@push.rocks/smartcli';
 | 
				
			|||||||
import * as smartpath from '@push.rocks/smartpath';
 | 
					import * as smartpath from '@push.rocks/smartpath';
 | 
				
			||||||
import * as smartpromise from '@push.rocks/smartpromise';
 | 
					import * as smartpromise from '@push.rocks/smartpromise';
 | 
				
			||||||
import * as smartupdate from '@push.rocks/smartupdate';
 | 
					import * as smartupdate from '@push.rocks/smartupdate';
 | 
				
			||||||
 | 
					import * as smartshell from '@push.rocks/smartshell';
 | 
				
			||||||
 | 
					import * as smartnetwork from '@push.rocks/smartnetwork';
 | 
				
			||||||
 | 
					import * as smartfile from '@push.rocks/smartfile';
 | 
				
			||||||
 | 
					import * as smartinteract from '@push.rocks/smartinteract';
 | 
				
			||||||
 | 
					import * as smartdelay from '@push.rocks/smartdelay';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
  smartlog,
 | 
					  smartlog,
 | 
				
			||||||
@@ -18,4 +23,9 @@ export {
 | 
				
			|||||||
  smartpath,
 | 
					  smartpath,
 | 
				
			||||||
  smartpromise,
 | 
					  smartpromise,
 | 
				
			||||||
  smartupdate,
 | 
					  smartupdate,
 | 
				
			||||||
 | 
					  smartshell,
 | 
				
			||||||
 | 
					  smartnetwork,
 | 
				
			||||||
 | 
					  smartfile,
 | 
				
			||||||
 | 
					  smartinteract,
 | 
				
			||||||
 | 
					  smartdelay,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,5 @@
 | 
				
			|||||||
    "baseUrl": ".",
 | 
					    "baseUrl": ".",
 | 
				
			||||||
    "paths": {}
 | 
					    "paths": {}
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "exclude": [
 | 
					  "exclude": ["dist_*/**/*.d.ts"]
 | 
				
			||||||
    "dist_*/**/*.d.ts"
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user